diff --git a/.vscode/launch.json b/.vscode/launch.json index 02533a07..f0d23921 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -34,8 +34,8 @@ }, "args": [ "--verbose", - "-runInBand", "--no-cache", + "--runInBand", "-c", "jestconfigs/jest.${input:TEST_TARGET}.config.js", "${input:TEST_FILE}" ] diff --git a/gulp/argv.js b/gulp/argv.js index 76561061..24088585 100644 --- a/gulp/argv.js +++ b/gulp/argv.js @@ -16,24 +16,25 @@ // under the License. const argv = require(`command-line-args`)([ - { name: `all`, type: Boolean }, - { name: 'verbose', alias: 'v', type: Boolean }, - { name: `target`, type: String, defaultValue: `` }, - { name: `module`, type: String, defaultValue: `` }, - { name: `coverage`, type: Boolean, defaultValue: false }, - { name: `targets`, alias: `t`, type: String, multiple: true, defaultValue: [] }, - { name: `modules`, alias: `m`, type: String, multiple: true, defaultValue: [] }, + { name: `all`, type: Boolean }, + { name: 'verbose', alias: 'v', type: Boolean }, + { name: `target`, type: String, defaultValue: `` }, + { name: `module`, type: String, defaultValue: `` }, + { name: `coverage`, type: Boolean, defaultValue: false }, + { name: `tests`, type: String, multiple: true, defaultValue: [`spec/*`] }, + { name: `targets`, alias: `t`, type: String, multiple: true, defaultValue: [] }, + { name: `modules`, alias: `m`, type: String, multiple: true, defaultValue: [] }, ], { partial: true }); const { targets, modules } = argv; if (argv.target === `src`) { - argv.target && !targets.length && targets.push(argv.target); + argv.target && !targets.length && targets.push(argv.target); } else { - argv.target && !targets.length && targets.push(argv.target); - argv.module && !modules.length && modules.push(argv.module); - (argv.all || !targets.length) && targets.push(`all`); - (argv.all || !modules.length) && modules.push(`all`); + argv.target && !targets.length && targets.push(argv.target); + argv.module && !modules.length && modules.push(argv.module); + (argv.all || !targets.length) && targets.push(`all`); + (argv.all || !modules.length) && modules.push(`all`); } module.exports = { argv, targets, modules }; diff --git a/gulp/test-task.js b/gulp/test-task.js index 60b0b552..31e7223b 100644 --- a/gulp/test-task.js +++ b/gulp/test-task.js @@ -34,29 +34,31 @@ if (targetAndModuleCombinations.length > 1) { const jest = path.join(path.parse(require.resolve(`jest`)).dir, `../bin/jest.js`); const testOptions = { - stdio: [`ignore`, `inherit`, `inherit`], - env: { - ...process.env, - // hide fs.promises/stream[Symbol.asyncIterator] warnings - NODE_NO_WARNINGS: `1`, - TS_JEST_DISABLE_VER_CHECKER: true - }, + stdio: [`ignore`, `inherit`, `inherit`], + env: { + ...process.env, + // hide fs.promises/stream[Symbol.asyncIterator] warnings + NODE_NO_WARNINGS: `1`, + TS_JEST_DISABLE_VER_CHECKER: true + }, }; const testTask = ((cache, execArgv, testOptions) => memoizeTask(cache, function test(target, format) { - const args = [...execArgv]; - const opts = { ...testOptions }; - if (argv.coverage) { - args.push(`-c`, `jest.coverage.config.js`, `--coverage`); - } else { - const cfgname = [target, format].filter(Boolean).join('.'); - args.push(`-c`, `jestconfigs/jest.${cfgname}.config.js`, `spec/*`); - } - opts.env = { ...opts.env, - TEST_DOM_STREAMS: (target ==='src' || format === 'umd').toString(), - TEST_NODE_STREAMS: (target ==='src' || format !== 'umd').toString(), - }; - return asyncDone(() => child_process.spawn(`node`, args, opts)); + const args = [...execArgv]; + const opts = { ...testOptions }; + if (argv.coverage) { + args.push(`-c`, `jest.coverage.config.js`, `--coverage`); + } else { + const cfgname = [target, format].filter(Boolean).join('.'); + // args.push(`--verbose`, `--no-cache`, `-i`); + args.push(`-c`, `jestconfigs/jest.${cfgname}.config.js`, ...argv.tests); + } + opts.env = { + ...opts.env, + TEST_DOM_STREAMS: (target === 'src' || format === 'umd').toString(), + TEST_NODE_STREAMS: (target === 'src' || format !== 'umd').toString(), + }; + return asyncDone(() => child_process.spawn(`node`, args, opts)); }))({}, [jest, ...jestArgv], testOptions); module.exports = testTask; diff --git a/gulp/util.js b/gulp/util.js index a0a28bdb..9e08bfce 100644 --- a/gulp/util.js +++ b/gulp/util.js @@ -21,18 +21,18 @@ const pump = require(`stream`).pipeline; const child_process = require(`child_process`); const { targets, modules } = require('./argv'); const { - ReplaySubject, - empty: ObservableEmpty, - throwError: ObservableThrow, - fromEvent: ObservableFromEvent + ReplaySubject, + empty: ObservableEmpty, + throwError: ObservableThrow, + fromEvent: ObservableFromEvent } = require('rxjs'); const { - merge, - flatMap, - takeUntil, - defaultIfEmpty, - multicast, - refCount, + merge, + flatMap, + takeUntil, + defaultIfEmpty, + multicast, + refCount, } = require('rxjs/operators'); const asyncDone = require('util').promisify(require('async-done')); @@ -45,198 +45,198 @@ const releasesRootDir = `targets`; const knownTargets = [`es5`, `es2015`, `esnext`]; const knownModules = [`cjs`, `esm`, `cls`, `umd`]; const tasksToSkipPerTargetOrFormat = { - src: { clean: true, build: true }, - cls: { test: true, package: true } + src: { clean: true, build: true }, + cls: { test: true, package: true } }; const packageJSONFields = [ `version`, `license`, `description`, `author`, `homepage`, `repository`, - `bugs`, `keywords`, `dependencies` + `bugs`, `keywords`, `dependencies` ]; const metadataFiles = [`LICENSE`, `readme.md`, `CHANGELOG.md`].map((filename) => { - let err = false, prefixes = [`./`, `../`]; - let p = prefixes.find((prefix) => { - try { - fs.statSync(path.resolve(path.join(prefix, filename))); - } catch (e) { return false; } - return true; - }); - if (!p) { - throw new Error(`Couldn't find ${filename} in ./ or ../`); - } - return path.join(p, filename); + let err = false, prefixes = [`./`, `../`]; + let p = prefixes.find((prefix) => { + try { + fs.statSync(path.resolve(path.join(prefix, filename))); + } catch (e) { return false; } + return true; + }); + if (!p) { + throw new Error(`Couldn't find ${filename} in ./ or ../`); + } + return path.join(p, filename); }); // see: https://github.com/google/closure-compiler/blob/c1372b799d94582eaf4b507a4a22558ff26c403c/src/com/google/javascript/jscomp/CompilerOptions.java#L2988 const gCCLanguageNames = { - es5: `ECMASCRIPT5`, - es2015: `ECMASCRIPT_2015`, - es2016: `ECMASCRIPT_2016`, - es2017: `ECMASCRIPT_2017`, - esnext: `ECMASCRIPT_NEXT` + es5: `ECMASCRIPT5`, + es2015: `ECMASCRIPT_2015`, + es2016: `ECMASCRIPT_2016`, + es2017: `ECMASCRIPT_2017`, + esnext: `ECMASCRIPT_NEXT` }; const UMDSourceTargets = { - es5: `es5`, - es2015: `es2015`, - es2016: `es2015`, - es2017: `es2015`, - esnext: `esnext` + es5: `es5`, + es2015: `es2015`, + es2016: `es2015`, + es2017: `es2015`, + esnext: `esnext` }; const terserLanguageNames = { - es5: 5, es2015: 6, - es2016: 7, es2017: 8, - esnext: 8 // <--- ? + es5: 5, es2015: 6, + es2016: 7, es2017: 8, + esnext: 8 // <--- ? }; // ES7+ keywords Terser shouldn't mangle // Hardcoded here since some are from ES7+, others are // only defined in interfaces, so difficult to get by reflection. const ESKeywords = [ - // GroupedIterable/GroupedAsyncIterable - `key`, - // PropertyDescriptors - `configurable`, `enumerable`, - // IteratorResult, Symbol.asyncIterator - `done`, `value`, `Symbol.asyncIterator`, `asyncIterator`, - // AsyncObserver - `values`, `hasError`, `hasCompleted`,`errorValue`, `closed`, - // Observable/Subscription/Scheduler - `next`, `error`, `complete`, `subscribe`, `unsubscribe`, `isUnsubscribed`, - // EventTarget - `addListener`, `removeListener`, `addEventListener`, `removeEventListener`, - // AbortController - `AbortController`, `AbortSignal`, `AbortError` + // GroupedIterable/GroupedAsyncIterable + `key`, + // PropertyDescriptors + `configurable`, `enumerable`, + // IteratorResult, Symbol.asyncIterator + `done`, `value`, `Symbol.asyncIterator`, `asyncIterator`, + // AsyncObserver + `values`, `hasError`, `hasCompleted`, `errorValue`, `closed`, + // Observable/Subscription/Scheduler + `next`, `error`, `complete`, `subscribe`, `unsubscribe`, `isUnsubscribed`, + // EventTarget + `addListener`, `removeListener`, `addEventListener`, `removeEventListener`, + // AbortController + `AbortController`, `AbortSignal`, `AbortError` ]; function taskName(target, format) { - return !format ? target : `${target}:${format}`; + return !format ? target : `${target}:${format}`; } function packageName(target, format) { - return !format ? target : `${target}-${format}`; + return !format ? target : `${target}-${format}`; } function tsconfigName(target, format) { - return !format ? target : `${target}.${format}`; + return !format ? target : `${target}.${format}`; } function targetDir(target, format) { - return path.join(releasesRootDir, ...(!format ? [target] : [target, format])); + return path.join(releasesRootDir, ...(!format ? [target] : [target, format])); } function shouldRunInChildProcess(target, format) { - // If we're building more than one module/target, then yes run this task in a child process - if (targets.length > 1 || modules.length > 1) { return true; } - // If the target we're building *isn't* the target the gulp command was configured to run, then yes run that in a child process - if (targets[0] !== target || modules[0] !== format) { return true; } - // Otherwise no need -- either gulp was run for just one target, or we've been spawned as the child of a multi-target parent gulp - return false; + // If we're building more than one module/target, then yes run this task in a child process + if (targets.length > 1 || modules.length > 1) { return true; } + // If the target we're building *isn't* the target the gulp command was configured to run, then yes run that in a child process + if (targets[0] !== target || modules[0] !== format) { return true; } + // Otherwise no need -- either gulp was run for just one target, or we've been spawned as the child of a multi-target parent gulp + return false; } const gulp = path.join(path.parse(require.resolve(`gulp`)).dir, `bin/gulp.js`); function spawnGulpCommandInChildProcess(command, target, format) { - const err = []; - return asyncDone(() => { - const child = child_process.spawn( - `node`, - [gulp, command, '-t', target, '-m', format, `-L`], - { - stdio: [`ignore`, `ignore`, `pipe`], - env: { ...process.env, NODE_NO_WARNINGS: `1` } - }); - child.stderr.on('data', (line) => err.push(line)); - return child; - }).catch(() => Promise.reject(err.length > 0 ? err.join('\n') - : `Error in "${command}:${taskName(target, format)}" task.`)); + const err = []; + return asyncDone(() => { + const child = child_process.spawn( + `node`, + [gulp, command, '-t', target, '-m', format, `-L`], + { + stdio: [`ignore`, `ignore`, `pipe`], + env: { ...process.env, NODE_NO_WARNINGS: `1` } + }); + child.stderr.on('data', (line) => err.push(line)); + return child; + }).catch(() => Promise.reject(err.length > 0 ? err.join('\n') + : `Error in "${command}:${taskName(target, format)}" task.`)); } -const logAndDie = (e) => { if (e) { process.exit(1) } }; +const logAndDie = (e) => { if (e) { console.error(e); process.exit(1) } }; function observableFromStreams(...streams) { - if (streams.length <= 0) { return ObservableEmpty(); } - const pumped = streams.length <= 1 ? streams[0] : pump(...streams, logAndDie); - const fromEvent = ObservableFromEvent.bind(null, pumped); - const streamObs = fromEvent(`data`).pipe( - merge(fromEvent(`error`).pipe(flatMap((e) => ObservableThrow(e)))), - takeUntil(fromEvent(`end`).pipe(merge(fromEvent(`close`)))), - defaultIfEmpty(`empty stream`), - multicast(new ReplaySubject()), - refCount()); - streamObs.stream = pumped; - streamObs.observable = streamObs; - return streamObs; + if (streams.length <= 0) { return ObservableEmpty(); } + const pumped = streams.length <= 1 ? streams[0] : pump(...streams, logAndDie); + const fromEvent = ObservableFromEvent.bind(null, pumped); + const streamObs = fromEvent(`data`).pipe( + merge(fromEvent(`error`).pipe(flatMap((e) => ObservableThrow(e)))), + takeUntil(fromEvent(`end`).pipe(merge(fromEvent(`close`)))), + defaultIfEmpty(`empty stream`), + multicast(new ReplaySubject()), + refCount()); + streamObs.stream = pumped; + streamObs.observable = streamObs; + return streamObs; } function* combinations(_targets, _modules) { - const targets = known(knownTargets, _targets || [`all`]); - const modules = known(knownModules, _modules || [`all`]); - - if (_targets.indexOf(`src`) > -1) { - yield [`src`, ``]; - return; - } + const targets = known(knownTargets, _targets || [`all`]); + const modules = known(knownModules, _modules || [`all`]); - if (_targets.indexOf(`all`) > -1 && _modules.indexOf(`all`) > -1) { - yield [`ts`, ``]; - yield [`src`, ``]; - yield [npmPkgName, ``]; - } + if (_targets.indexOf(`src`) > -1) { + yield [`src`, ``]; + return; + } - for (const format of modules) { - for (const target of targets) { - yield [target, format]; - } - } + if (_targets.indexOf(`all`) > -1 && _modules.indexOf(`all`) > -1) { + yield [`ts`, ``]; + yield [`src`, ``]; + yield [npmPkgName, ``]; + } - function known(known, values) { - return ~values.indexOf(`all`) ? known - : ~values.indexOf(`src`) ? [`src`] - : Object.keys( - values.reduce((map, arg) => (( - (known.indexOf(arg) !== -1) && - (map[arg.toLowerCase()] = true) - || true) && map - ), {}) - ).sort((a, b) => known.indexOf(a) - known.indexOf(b)); + for (const format of modules) { + for (const target of targets) { + yield [target, format]; } + } + + function known(known, values) { + return ~values.indexOf(`all`) ? known + : ~values.indexOf(`src`) ? [`src`] + : Object.keys( + values.reduce((map, arg) => (( + (known.indexOf(arg) !== -1) && + (map[arg.toLowerCase()] = true) + || true) && map + ), {}) + ).sort((a, b) => known.indexOf(a) - known.indexOf(b)); + } } const esmRequire = require(`esm`)(module, { - mode: `auto`, - cjs: { - /* A boolean for storing ES modules in require.cache. */ - cache: true, - /* A boolean for respecting require.extensions in ESM. */ - extensions: true, - /* A boolean for __esModule interoperability. */ - interop: true, - /* A boolean for importing named exports of CJS modules. */ - namedExports: true, - /* A boolean for following CJS path rules in ESM. */ - paths: true, - /* A boolean for __dirname, __filename, and require in ESM. */ - vars: true, - } + mode: `auto`, + cjs: { + /* A boolean for storing ES modules in require.cache. */ + cache: true, + /* A boolean for respecting require.extensions in ESM. */ + extensions: true, + /* A boolean for __esModule interoperability. */ + interop: true, + /* A boolean for importing named exports of CJS modules. */ + namedExports: true, + /* A boolean for following CJS path rules in ESM. */ + paths: true, + /* A boolean for __dirname, __filename, and require in ESM. */ + vars: true, + } }); const getUMDExportName = (umdEntryFileName) => umdEntryFileName - .split('.') - .filter((x) => x != 'dom') - .map((x) => x[0].toUpperCase() + x.slice(1)) - .join(''); + .split('.') + .filter((x) => x != 'dom') + .map((x) => x[0].toUpperCase() + x.slice(1)) + .join(''); module.exports = { - mainExport, npmPkgName, npmOrgName, metadataFiles, packageJSONFields, + mainExport, npmPkgName, npmOrgName, metadataFiles, packageJSONFields, - knownTargets, knownModules, tasksToSkipPerTargetOrFormat, - gCCLanguageNames, UMDSourceTargets, terserLanguageNames, + knownTargets, knownModules, tasksToSkipPerTargetOrFormat, + gCCLanguageNames, UMDSourceTargets, terserLanguageNames, - taskName, packageName, tsconfigName, targetDir, combinations, observableFromStreams, - ESKeywords, esmRequire, shouldRunInChildProcess, spawnGulpCommandInChildProcess, getUMDExportName, + taskName, packageName, tsconfigName, targetDir, combinations, observableFromStreams, + ESKeywords, esmRequire, shouldRunInChildProcess, spawnGulpCommandInChildProcess, getUMDExportName, - targetAndModuleCombinations: [...combinations(targets, modules)] + targetAndModuleCombinations: [...combinations(targets, modules)] }; diff --git a/jest.config.js b/jest.config.js index 01de06ad..d4f2fd9f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,42 +16,42 @@ // under the License. module.exports = { - "verbose": false, - "testEnvironment": "node", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "spec/tsconfig.json" - } - }, - "rootDir": "./", - "roots": [ - "/spec/" - ], - "moduleFileExtensions": [ - "js", - "ts", - "tsx" - ], - "coverageReporters": [ - "lcov" - ], - "coveragePathIgnorePatterns": [ - "spec\\/.*\\.(ts|tsx|js)$", - "/node_modules/" - ], - "transform": { - "^.+\\.jsx?$": "ts-jest", - "^.+\\.tsx?$": "ts-jest" - }, - "transformIgnorePatterns": [ - "/(es5|es2015|esnext)/umd/", - "/node_modules/(?!web-stream-tools).+\\.js$" - ], - "testRegex": "(.*(-|\\.)(test|spec)s?)\\.(ts|tsx|js)$", - "preset": "ts-jest", - "testMatch": null, - "moduleNameMapper": { - "^ix(.*)": "/src/$1.js" + "verbose": false, + "testEnvironment": "node", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "spec/tsconfig.json" } + }, + "rootDir": "./", + "roots": [ + "/spec/" + ], + "moduleFileExtensions": [ + "js", + "ts", + "tsx" + ], + "coverageReporters": [ + "lcov" + ], + "coveragePathIgnorePatterns": [ + "spec\\/.*\\.(ts|tsx|js)$", + "/node_modules/" + ], + "transform": { + "^.+\\.jsx?$": "ts-jest", + "^.+\\.tsx?$": "ts-jest" + }, + "transformIgnorePatterns": [ + "/(es5|es2015|esnext)/umd/", + "/node_modules/(?!web-stream-tools).+\\.js$" + ], + "testRegex": "(.*(-|\\.)(test|spec)s?)\\.(ts|tsx|js)$", + "preset": "ts-jest", + "testMatch": null, + "moduleNameMapper": { + "^ix(.*)": "/src/$1.js" + } }; diff --git a/jestconfigs/jest.es2015.cjs.config.js b/jestconfigs/jest.es2015.cjs.config.js index df623ff3..cbde3b52 100644 --- a/jestconfigs/jest.es2015.cjs.config.js +++ b/jestconfigs/jest.es2015.cjs.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es2015.cjs.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/es2015/cjs$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es2015.cjs.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/es2015/cjs$1" + } }; diff --git a/jestconfigs/jest.es2015.esm.config.js b/jestconfigs/jest.es2015.esm.config.js index ac41583f..4a5d03b8 100644 --- a/jestconfigs/jest.es2015.esm.config.js +++ b/jestconfigs/jest.es2015.esm.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es2015.esm.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/es2015/esm$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es2015.esm.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/es2015/esm$1" + } }; diff --git a/jestconfigs/jest.es2015.umd.config.js b/jestconfigs/jest.es2015.umd.config.js index 0ec33828..d074b7b4 100644 --- a/jestconfigs/jest.es2015.umd.config.js +++ b/jestconfigs/jest.es2015.umd.config.js @@ -1,17 +1,17 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es2015.umd.json" - } - }, - "moduleNameMapper": { - "^ix/asynciterable/operators(.*)": "/targets/es2015/umd/Ix.dom.asynciterable.operators.js", - "^ix/asynciterable(.*)": "/targets/es2015/umd/Ix.dom.asynciterable.js", - "^ix/iterable/operators(.*)": "/targets/es2015/umd/Ix.dom.iterable.operators.js", - "^ix/iterable(.*)": "/targets/es2015/umd/Ix.dom.iterable.js", - "^ix(.*)": "/targets/es2015/umd/Ix.dom.js" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es2015.umd.json" } + }, + "moduleNameMapper": { + "^ix/asynciterable/operators(.*)": "/targets/es2015/umd/Ix.dom.asynciterable.operators.js", + "^ix/asynciterable(.*)": "/targets/es2015/umd/Ix.dom.asynciterable.js", + "^ix/iterable/operators(.*)": "/targets/es2015/umd/Ix.dom.iterable.operators.js", + "^ix/iterable(.*)": "/targets/es2015/umd/Ix.dom.iterable.js", + "^ix(.*)": "/targets/es2015/umd/Ix.dom.js" + } }; diff --git a/jestconfigs/jest.es5.cjs.config.js b/jestconfigs/jest.es5.cjs.config.js index 81d610dc..3a2e1748 100644 --- a/jestconfigs/jest.es5.cjs.config.js +++ b/jestconfigs/jest.es5.cjs.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es5.cjs.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/es5/cjs$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es5.cjs.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/es5/cjs$1" + } }; diff --git a/jestconfigs/jest.es5.esm.config.js b/jestconfigs/jest.es5.esm.config.js index a1a774bf..d15cf5b9 100644 --- a/jestconfigs/jest.es5.esm.config.js +++ b/jestconfigs/jest.es5.esm.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es5.esm.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/es5/esm$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es5.esm.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/es5/esm$1" + } }; diff --git a/jestconfigs/jest.es5.umd.config.js b/jestconfigs/jest.es5.umd.config.js index cb2dfd06..3b5e4a87 100644 --- a/jestconfigs/jest.es5.umd.config.js +++ b/jestconfigs/jest.es5.umd.config.js @@ -1,17 +1,17 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.es5.umd.json" - } - }, - "moduleNameMapper": { - "^ix/asynciterable/operators(.*)": "/targets/es5/umd/Ix.dom.asynciterable.operators.js", - "^ix/asynciterable(.*)": "/targets/es5/umd/Ix.dom.asynciterable.js", - "^ix/iterable/operators(.*)": "/targets/es5/umd/Ix.dom.iterable.operators.js", - "^ix/iterable(.*)": "/targets/es5/umd/Ix.dom.iterable.js", - "^ix(.*)": "/targets/es5/umd/Ix.dom.js" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.es5.umd.json" } + }, + "moduleNameMapper": { + "^ix/asynciterable/operators(.*)": "/targets/es5/umd/Ix.dom.asynciterable.operators.js", + "^ix/asynciterable(.*)": "/targets/es5/umd/Ix.dom.asynciterable.js", + "^ix/iterable/operators(.*)": "/targets/es5/umd/Ix.dom.iterable.operators.js", + "^ix/iterable(.*)": "/targets/es5/umd/Ix.dom.iterable.js", + "^ix(.*)": "/targets/es5/umd/Ix.dom.js" + } }; diff --git a/jestconfigs/jest.esnext.cjs.config.js b/jestconfigs/jest.esnext.cjs.config.js index c65466e5..20ca7a27 100644 --- a/jestconfigs/jest.esnext.cjs.config.js +++ b/jestconfigs/jest.esnext.cjs.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.esnext.cjs.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/esnext/cjs$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.esnext.cjs.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/esnext/cjs$1" + } }; diff --git a/jestconfigs/jest.esnext.esm.config.js b/jestconfigs/jest.esnext.esm.config.js index 936c3fc8..5f25e5a4 100644 --- a/jestconfigs/jest.esnext.esm.config.js +++ b/jestconfigs/jest.esnext.esm.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.esnext.esm.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/esnext/esm$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.esnext.esm.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/esnext/esm$1" + } }; diff --git a/jestconfigs/jest.esnext.umd.config.js b/jestconfigs/jest.esnext.umd.config.js index 2622d976..be43b59b 100644 --- a/jestconfigs/jest.esnext.umd.config.js +++ b/jestconfigs/jest.esnext.umd.config.js @@ -1,17 +1,17 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.esnext.umd.json" - } - }, - "moduleNameMapper": { - "^ix/asynciterable/operators(.*)": "/targets/esnext/umd/Ix.dom.asynciterable.operators.js", - "^ix/asynciterable(.*)": "/targets/esnext/umd/Ix.dom.asynciterable.js", - "^ix/iterable/operators(.*)": "/targets/esnext/umd/Ix.dom.iterable.operators.js", - "^ix/iterable(.*)": "/targets/esnext/umd/Ix.dom.iterable.js", - "^ix(.*)": "/targets/esnext/umd/Ix.dom.js" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.esnext.umd.json" } + }, + "moduleNameMapper": { + "^ix/asynciterable/operators(.*)": "/targets/esnext/umd/Ix.dom.asynciterable.operators.js", + "^ix/asynciterable(.*)": "/targets/esnext/umd/Ix.dom.asynciterable.js", + "^ix/iterable/operators(.*)": "/targets/esnext/umd/Ix.dom.iterable.operators.js", + "^ix/iterable(.*)": "/targets/esnext/umd/Ix.dom.iterable.js", + "^ix(.*)": "/targets/esnext/umd/Ix.dom.js" + } }; diff --git a/jestconfigs/jest.ix.config.js b/jestconfigs/jest.ix.config.js index 9e7c5d4c..b2497120 100644 --- a/jestconfigs/jest.ix.config.js +++ b/jestconfigs/jest.ix.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.ix.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/ix$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.ix.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/ix$1" + } }; diff --git a/jestconfigs/jest.src.config.js b/jestconfigs/jest.src.config.js index fae1ce1f..4fbcb52f 100644 --- a/jestconfigs/jest.src.config.js +++ b/jestconfigs/jest.src.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.src.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/src$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.src.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/src$1" + } }; diff --git a/jestconfigs/jest.ts.config.js b/jestconfigs/jest.ts.config.js index 7da07800..437ebfa2 100644 --- a/jestconfigs/jest.ts.config.js +++ b/jestconfigs/jest.ts.config.js @@ -1,13 +1,13 @@ module.exports = { - ...require('../jest.config'), - "rootDir": "../", - "globals": { - "ts-jest": { - "diagnostics": false, - "tsConfig": "/spec/tsconfig/tsconfig.ts.json" - } - }, - "moduleNameMapper": { - "^ix(.*)": "/targets/ts$1" + ...require('../jest.config'), + "rootDir": "../", + "globals": { + "ts-jest": { + "diagnostics": false, + "tsconfig": "/spec/tsconfig/tsconfig.ts.json" } + }, + "moduleNameMapper": { + "^ix(.*)": "/targets/ts$1" + } }; diff --git a/package.json b/package.json index 8e59eccc..bcae4839 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,9 @@ }, "devDependencies": { "@types/glob": "7.1.1", - "@types/jest": "25.1.3", - "@typescript-eslint/eslint-plugin": "4.26.0", - "@typescript-eslint/parser": "4.26.0", + "@types/jest": "27.4.0", + "@typescript-eslint/eslint-plugin": "5.12.0", + "@typescript-eslint/parser": "5.12.0", "abortcontroller-polyfill": "1.4.0", "async-done": "1.3.2", "benchmark": "2.1.4", @@ -66,20 +66,20 @@ "coveralls": "3.0.9", "cz-conventional-changelog": "3.1.0", "del": "5.1.0", - "eslint": "7.27.0", - "eslint-plugin-jest": "24.3.6", - "esm": "3.2.25", + "eslint": "8.9.0", + "eslint-plugin-jest": "26.1.1", + "esm": "https://github.com/jsg2021/esm/releases/download/v3.x.x-pr883/esm-3.x.x-pr883.tgz", "glob": "7.1.6", - "google-closure-compiler": "20210601.0.0", + "google-closure-compiler": "20220202.0.0", "gulp": "4.0.2", "gulp-json-transform": "0.4.7", "gulp-rename": "2.0.0", "gulp-sourcemaps": "2.6.5", "gulp-typescript": "5.0.1", "husky": "4.2.3", - "jest": "25.1.0", + "jest": "27.5.1", "jest-environment-node-debug": "2.0.0", - "jest-silent-reporter": "0.2.1", + "jest-silent-reporter": "0.5.0", "json": "9.0.6", "lerna": "3.20.2", "lint-staged": "10.0.7", @@ -95,10 +95,10 @@ "source-map-loader": "0.2.4", "terser": "4.6.4", "terser-webpack-plugin": "2.3.5", - "ts-jest": "25.2.1", + "ts-jest": "27.1.3", "ts-node": "8.6.2", "typedoc": "0.16.10", - "typescript": "4.3.5", + "typescript": "4.6.2", "validate-commit-msg": "2.14.0", "web-stream-tools": "0.0.1", "web-streams-polyfill": "2.1.0", diff --git a/spec/asynciterable-operators/batch-spec.ts b/spec/asynciterable-operators/batch-spec.ts index fbfc678d..ef5437f8 100644 --- a/spec/asynciterable-operators/batch-spec.ts +++ b/spec/asynciterable-operators/batch-spec.ts @@ -2,7 +2,7 @@ import '../asynciterablehelpers'; import { batch } from 'ix/asynciterable/operators'; import { AsyncSink } from 'ix/asynciterable'; -const delay = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms)); +const delay = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms)); test('AsyncIterable#batch basic', async () => { const sink = new AsyncSink(); @@ -26,10 +26,10 @@ test('AsyncIterable#batch basic', async () => { await delay(); expect(await it.next()).toEqual({ done: false, - value: [4, 5] + value: [4, 5], }); expect(await it.next()).toEqual({ - done: true + done: true, }); }); diff --git a/spec/asynciterable-operators/concatall-spec.ts b/spec/asynciterable-operators/concatall-spec.ts index 6952683b..fb9e8631 100644 --- a/spec/asynciterable-operators/concatall-spec.ts +++ b/spec/asynciterable-operators/concatall-spec.ts @@ -10,12 +10,12 @@ test('AsyncIterable#concat concatAll behavior', async () => { test('AsyncIterable#concat concatAll order of effects', async () => { let i = 0; const xss = range(0, 3).pipe( - map(x => range(0, x + 1)), + map((x) => range(0, x + 1)), tap({ next: async () => ++i }) ); const res = xss.pipe( concatAll(), - map(x => i + ' - ' + x) + map((x) => i + ' - ' + x) ); expect( diff --git a/spec/asynciterable-operators/debounce-spec.ts b/spec/asynciterable-operators/debounce-spec.ts index e367c5e5..32915a93 100644 --- a/spec/asynciterable-operators/debounce-spec.ts +++ b/spec/asynciterable-operators/debounce-spec.ts @@ -52,7 +52,7 @@ test( const controller = new AbortController(); const it = ys[Symbol.asyncIterator](controller.signal); await hasNext(it, 1); - controller.abort(); + setImmediate(() => controller.abort()); await expect(hasNext(it, 3)).rejects.toThrow(AbortError); await noNext(it); }, diff --git a/spec/asynciterable-operators/expand-spec.ts b/spec/asynciterable-operators/expand-spec.ts index 8994c781..63b67061 100644 --- a/spec/asynciterable-operators/expand-spec.ts +++ b/spec/asynciterable-operators/expand-spec.ts @@ -4,14 +4,14 @@ import { expand, take } from 'ix/asynciterable/operators'; test('AsyncIterable#expand with single return behavior', async () => { const res = of(0).pipe( - expand(async x => of(x + 1)), + expand(async (x) => of(x + 1)), take(10) ); expect(await sequenceEqual(res, range(0, 10))).toBeTruthy(); }); test('AsyncIterable#expand with range return behavior', async () => { - const res = of(3).pipe(expand(async x => range(0, x))); + const res = of(3).pipe(expand(async (x) => range(0, x))); const exp = of(3, 0, 1, 2, 0, 0, 1, 0); expect(await sequenceEqual(res, exp)).toBeTruthy(); diff --git a/spec/asynciterable-operators/flat-spec.ts b/spec/asynciterable-operators/flat-spec.ts index 4362d72f..5a760e73 100644 --- a/spec/asynciterable-operators/flat-spec.ts +++ b/spec/asynciterable-operators/flat-spec.ts @@ -7,15 +7,22 @@ function compareArrays(fst: Iterable, snd: Iterable) { } test('AsyncIterable#flat flattens all', async () => { - const xs = of(1, of(2, of(3)), 4); + const xs = of(1, of(2, of(3)), 4); const ys = await toArray(xs.pipe(flat())); + compareArrays(ys, [1, 4, 2, 3]); +}); + +test('AsyncIterable#flat flattens all with concurrent = 1', async () => { + const xs = of(1, of(2, of(3)), 4); + const ys = await toArray(xs.pipe(flat(-1, 1))); + compareArrays(ys, [1, 2, 3, 4]); }); test('AsyncIterable#flat flattens two layers', async () => { - const xs = of(1, of(2, of(3)), 4); + const xs = of(1, of(2, of(3)), 4); const ys = await toArray(xs.pipe(flat(2))); - compareArrays(ys, [1, 2, 3, 4]); + compareArrays(ys, [1, 4, 2, 3]); }); diff --git a/spec/asynciterable-operators/ignoreelements.ts b/spec/asynciterable-operators/ignoreelements.ts index 48885b08..33b4f497 100644 --- a/spec/asynciterable-operators/ignoreelements.ts +++ b/spec/asynciterable-operators/ignoreelements.ts @@ -9,7 +9,7 @@ test('Iterable#ignoreElements has side effects', async () => { tap({ next: async () => { n++; - } + }, }), ignoreElements(), take(5) diff --git a/spec/asynciterable-operators/mergeall-spec.ts b/spec/asynciterable-operators/mergeall-spec.ts index 2bc130a8..b08b8f6a 100644 --- a/spec/asynciterable-operators/mergeall-spec.ts +++ b/spec/asynciterable-operators/mergeall-spec.ts @@ -1,8 +1,8 @@ import '../asynciterablehelpers'; -import { of, sequenceEqual } from 'ix/asynciterable'; +import { of, toArray } from 'ix/asynciterable'; import { mergeAll } from 'ix/asynciterable/operators'; test('AsyncIterable#merge mergeAll behavior', async () => { const res = of(of(1, 2, 3), of(4, 5)).pipe(mergeAll()); - expect(await sequenceEqual(res, of(1, 2, 3, 4, 5))).toBeTruthy(); + expect(await toArray(res)).toEqual([1, 2, 4, 3, 5]); }); diff --git a/spec/asynciterable-operators/skipuntil-spec.ts b/spec/asynciterable-operators/skipuntil-spec.ts index 9822f630..efc7b081 100644 --- a/spec/asynciterable-operators/skipuntil-spec.ts +++ b/spec/asynciterable-operators/skipuntil-spec.ts @@ -3,7 +3,7 @@ import { skipUntil } from 'ix/asynciterable/operators'; import { as } from 'ix/asynciterable'; test('AsyncIterable#skipUntil hits', async () => { - const xs = async function*() { + const xs = async function* () { yield await delayValue(1, 100); yield await delayValue(2, 300); yield await delayValue(3, 600); @@ -17,7 +17,7 @@ test('AsyncIterable#skipUntil hits', async () => { }); test('AsyncIterable#skipUntil misses', async () => { - const xs = async function*() { + const xs = async function* () { yield await delayValue(1, 400); yield await delayValue(2, 500); yield await delayValue(3, 600); diff --git a/spec/asynciterable-operators/startwith-spec.ts b/spec/asynciterable-operators/startwith-spec.ts index 3f64a423..9acda2ff 100644 --- a/spec/asynciterable-operators/startwith-spec.ts +++ b/spec/asynciterable-operators/startwith-spec.ts @@ -14,14 +14,9 @@ test('AsyncIterable#startWith adds without causing effects', async () => { tap({ next: async () => { oops = true; - } + }, }) ); - await toArray( - e.pipe( - startWith(0), - take(1) - ) - ); + await toArray(e.pipe(startWith(0), take(1))); expect(oops).toBeFalsy(); }); diff --git a/spec/asynciterable-operators/switchmap-spec.ts b/spec/asynciterable-operators/switchmap-spec.ts new file mode 100644 index 00000000..a5f8cf6b --- /dev/null +++ b/spec/asynciterable-operators/switchmap-spec.ts @@ -0,0 +1,47 @@ +import '../asynciterablehelpers'; +import { of, toArray } from 'ix/asynciterable'; +import { switchMap, delayEach, tap } from 'ix/asynciterable/operators'; + +describe(`AsyncIterable#switchMap`, () => { + test('switches inner sequences', async () => { + const outerValues = new Array(); + const innerValues = new Array(); + + const xs = of(0, 1, 2).pipe( + delayEach(500), + tap((x) => outerValues.push(x)) + ); + const ys = of('0', '1', '2', '3').pipe( + delayEach(200), + tap((x) => innerValues.push(x)) + ); + const source = xs.pipe(switchMap(() => ys)); + const expected = [ + '0', + '1', // xs=0 + '0', + '1', // xs=1 + '0', + '1', + '2', + '3', // xs=2 + ]; + + expect(await toArray(source)).toEqual(expected); + + expect(innerValues).toEqual(expected); + expect(outerValues).toEqual([0, 1, 2]); + }); + + test(`supports projecting to Arrays`, async () => { + const xs = of(0, 1, 2).pipe(delayEach(100)); + const source = xs.pipe(switchMap(() => [0, 1, 2])); + expect(await toArray(source)).toEqual([0, 1, 2, 0, 1, 2, 0, 1, 2]); + }); + + test(`supports projecting to Promise`, async () => { + const xs = of(0, 1, 2).pipe(delayEach(100)); + const source = xs.pipe(switchMap(async () => [0, 1, 2])); + expect(await toArray(source)).toEqual([0, 1, 2, 0, 1, 2, 0, 1, 2]); + }); +}); diff --git a/spec/asynciterable-operators/throttle-spec.ts b/spec/asynciterable-operators/throttle-spec.ts index ec4e9424..235b3a45 100644 --- a/spec/asynciterable-operators/throttle-spec.ts +++ b/spec/asynciterable-operators/throttle-spec.ts @@ -3,7 +3,7 @@ import { throttle } from 'ix/asynciterable/operators'; import { as } from 'ix/asynciterable'; test('AsyncIterable#throttle drops none', async () => { - const xs = async function*() { + const xs = async function* () { yield await delayValue(1, 100); yield await delayValue(2, 100); yield await delayValue(3, 100); @@ -18,7 +18,7 @@ test('AsyncIterable#throttle drops none', async () => { }); test('AsyncIterable#throttle drops some', async () => { - const xs = async function*() { + const xs = async function* () { yield await delayValue(1, 200); yield await delayValue(2, 200); yield await delayValue(3, 200); diff --git a/spec/asynciterable-operators/todomstream-spec.ts b/spec/asynciterable-operators/todomstream-spec.ts index e5929c56..fc7c0e0f 100644 --- a/spec/asynciterable-operators/todomstream-spec.ts +++ b/spec/asynciterable-operators/todomstream-spec.ts @@ -10,9 +10,9 @@ import { map, toDOMStream } from 'ix/asynciterable/operators'; }); } - const stringsItr = () => from([1, 2, 3]).pipe(map(i => `${i}`)); - const buffersItr = () => stringsItr().pipe(map(val => Buffer.from(val))); - const objectsItr = () => stringsItr().pipe(map(val => ({ val }))); + const stringsItr = () => from([1, 2, 3]).pipe(map((i) => `${i}`)); + const buffersItr = () => stringsItr().pipe(map((val) => Buffer.from(val))); + const objectsItr = () => stringsItr().pipe(map((val) => ({ val }))); const compare = (a: T, b: T) => { const aVal = ArrayBuffer.isView(a) ? `${Buffer.from(a.buffer, a.byteOffset, a.byteLength)}` : a; const bVal = ArrayBuffer.isView(b) ? `${Buffer.from(b.buffer, b.byteOffset, b.byteLength)}` : b; @@ -28,8 +28,8 @@ import { map, toDOMStream } from 'ix/asynciterable/operators'; describe('AsyncIterable#toDOMStream', () => { describe('DefaultController', () => { const expectedStrings = ['1', '2', '3']; - const expectedObjects = expectedStrings.map(val => ({ val })); - const expectedBuffers = expectedStrings.map(x => Buffer.from(x)); + const expectedObjects = expectedStrings.map((val) => ({ val })); + const expectedBuffers = expectedStrings.map((x) => Buffer.from(x)); test('yields Strings', async () => { const expected = from(expectedStrings); const actual = stringsItr().pipe(toDOMStream()); @@ -49,11 +49,11 @@ import { map, toDOMStream } from 'ix/asynciterable/operators'; describe('ReadableByteStreamController (byobRequest)', () => { const expectedStrings = ['123']; - const expectedBuffers = expectedStrings.map(x => Buffer.from(x)); + const expectedBuffers = expectedStrings.map((x) => Buffer.from(x)); test('yields Strings', async () => { const expected = from(expectedBuffers); const actual = stringsItr() - .pipe(map(x => Buffer.from(x))) + .pipe(map((x) => Buffer.from(x))) .pipe(toDOMStream({ type: 'bytes' })); await expect(actual).toEqualStream(expected, compare); }); @@ -66,11 +66,11 @@ import { map, toDOMStream } from 'ix/asynciterable/operators'; describe('ReadableByteStreamController (autoAllocateChunkSize)', () => { const expectedStrings = ['123']; - const expectedBuffers = expectedStrings.map(x => Buffer.from(x)); + const expectedBuffers = expectedStrings.map((x) => Buffer.from(x)); test('yields Strings', async () => { const expected = from(expectedBuffers); const actual = stringsItr() - .pipe(map(x => Buffer.from(x))) + .pipe(map((x) => Buffer.from(x))) .pipe(toDOMStream({ type: 'bytes', autoAllocateChunkSize: 1024 })); await expect(actual).toEqualStream(expected, compare); }); diff --git a/spec/asynciterable/aborting-spec.ts b/spec/asynciterable/aborting-spec.ts index 8a303b53..908732fe 100644 --- a/spec/asynciterable/aborting-spec.ts +++ b/spec/asynciterable/aborting-spec.ts @@ -6,13 +6,13 @@ test("Abort signal isn't overloaded with event listeners", async () => { const abortController = new AbortController(); const listeners: (() => void)[] = []; // eslint-disable-next-line jest/no-jasmine-globals - spyOn(abortController.signal, 'addEventListener').and.callFake((_, listener) => - listeners.push(listener) - ); + jest + .spyOn(abortController.signal, 'addEventListener') + .mockImplementation((_, listener) => listeners.push(listener as any)); // eslint-disable-next-line jest/no-jasmine-globals - spyOn(abortController.signal, 'removeEventListener').and.callFake((_, listener) => - listeners.splice(listeners.indexOf(listener), 1) - ); + jest + .spyOn(abortController.signal, 'removeEventListener') + .mockImplementation((_, listener) => listeners.splice(listeners.indexOf(listener as any), 1)); await interval(10) .pipe(take(10)) .forEach( diff --git a/spec/asynciterable/as-spec.ts b/spec/asynciterable/as-spec.ts index 3a26190e..9c982043 100644 --- a/spec/asynciterable/as-spec.ts +++ b/spec/asynciterable/as-spec.ts @@ -22,7 +22,7 @@ test('AsyncIterable#as from promise list', async () => { const xs: Iterable> = [ Promise.resolve(1), Promise.resolve(2), - Promise.resolve(3) + Promise.resolve(3), ]; const res = as(xs); diff --git a/spec/asynciterable/foreach-spec.ts b/spec/asynciterable/foreach-spec.ts index 0f7bb30d..d0bb3201 100644 --- a/spec/asynciterable/foreach-spec.ts +++ b/spec/asynciterable/foreach-spec.ts @@ -4,7 +4,7 @@ import { range } from 'ix/asynciterable'; test('AsyncIterable#forEach', async () => { let n = 0; - await range(5, 3).forEach(async x => { + await range(5, 3).forEach(async (x) => { n += x; }); diff --git a/spec/asynciterable/fromeventpattern-spec.ts b/spec/asynciterable/fromeventpattern-spec.ts index 9a1f59c3..1cfefdc1 100644 --- a/spec/asynciterable/fromeventpattern-spec.ts +++ b/spec/asynciterable/fromeventpattern-spec.ts @@ -7,8 +7,8 @@ const EVENT_TYPE = 'data'; test('AsyncIterable#fromEventPattern writes before emit', async () => { const e = new EventEmitter(); const a = fromEventPattern( - h => e.addListener(EVENT_TYPE, h), - h => e.removeListener(EVENT_TYPE, h) + (h) => e.addListener(EVENT_TYPE, h), + (h) => e.removeListener(EVENT_TYPE, h) ); e.emit(EVENT_TYPE, 1); diff --git a/spec/asynciterablehelpers.ts b/spec/asynciterablehelpers.ts index 2a076ea2..db59ea70 100644 --- a/spec/asynciterablehelpers.ts +++ b/spec/asynciterablehelpers.ts @@ -14,7 +14,7 @@ export async function noNext(source: AsyncIterator) { } export function delayValue(item: T, delay: number): Promise { - return new Promise(res => { + return new Promise((res) => { setTimeout(() => { res(item); }, delay); @@ -36,13 +36,13 @@ export function toObserver( return { next: (observer.next || noop).bind(observer), error: (observer.error || noop).bind(observer), - complete: (observer.complete || noop).bind(observer) + complete: (observer.complete || noop).bind(observer), }; } else { return { next: typeof next === 'function' ? next : noop, error: typeof error === 'function' ? error : noop, - complete: typeof complete === 'function' ? complete : noop + complete: typeof complete === 'function' ? complete : noop, }; } } @@ -108,7 +108,7 @@ expect.extend({ results.push(`expected length ${expectedCount}, instead received ${++actualCount}`); } return { pass: results.length === 0, message: () => results.join('\n') }; - } + }, }); function getValueByteLength(value: any) { diff --git a/spec/iterable-operators/concatall-spec.ts b/spec/iterable-operators/concatall-spec.ts index a3edc5f1..424e89a1 100644 --- a/spec/iterable-operators/concatall-spec.ts +++ b/spec/iterable-operators/concatall-spec.ts @@ -10,12 +10,12 @@ test('Iterable#concat concatAll behavior', () => { test('Iterable#concat concatAll order of effects', () => { let i = 0; const xss = range(0, 3).pipe( - map(x => range(0, x + 1)), + map((x) => range(0, x + 1)), tap({ next: async () => ++i }) ); const res = xss.pipe( concatAll(), - map(x => i + ' - ' + x) + map((x) => i + ' - ' + x) ); expect(sequenceEqual(res, of('1 - 0', '2 - 0', '2 - 1', '3 - 0', '3 - 1', '3 - 2'))).toBeTruthy(); diff --git a/spec/iterable-operators/expand-spec.ts b/spec/iterable-operators/expand-spec.ts index 6c6878bb..4804c9b6 100644 --- a/spec/iterable-operators/expand-spec.ts +++ b/spec/iterable-operators/expand-spec.ts @@ -3,13 +3,13 @@ import { expand, take } from 'ix/iterable/operators'; import { range, sequenceEqual } from 'ix/iterable'; test('Iterable#expand with single return behavior', () => { - const src = expand(x => [x + 1])([0]); + const src = expand((x) => [x + 1])([0]); const res = src.pipe(take(10)); expect(sequenceEqual(res, range(0, 10))).toBeTruthy(); }); test('Iterable#expand with range return behavior', () => { - const res = expand(x => range(0, x))([3]); + const res = expand((x) => range(0, x))([3]); const exp = [3, 0, 1, 2, 0, 0, 1, 0]; expect(sequenceEqual(res, exp)).toBeTruthy(); diff --git a/spec/iterable-operators/foreach-spec.ts b/spec/iterable-operators/foreach-spec.ts index 47827392..c0c2cf98 100644 --- a/spec/iterable-operators/foreach-spec.ts +++ b/spec/iterable-operators/foreach-spec.ts @@ -4,7 +4,7 @@ import { range } from 'ix/iterable'; test('Iterable#forEach', () => { let n = 0; - range(5, 3).forEach(x => (n += x)); + range(5, 3).forEach((x) => (n += x)); expect(5 + 6 + 7).toBe(n); }); diff --git a/spec/iterable-operators/orderby-spec.ts b/spec/iterable-operators/orderby-spec.ts index fedb96c9..4c71e68f 100644 --- a/spec/iterable-operators/orderby-spec.ts +++ b/spec/iterable-operators/orderby-spec.ts @@ -4,7 +4,7 @@ import { orderBy, orderByDescending, thenBy, thenByDescending } from 'ix/iterabl test('Iterable#orderBy normal ordering', () => { const xs = [2, 6, 1, 5, 7, 8, 9, 3, 4, 0]; - const ys = from(xs).pipe(orderBy(x => x)); + const ys = from(xs).pipe(orderBy((x) => x)); const it = ys[Symbol.iterator](); for (let i = 0; i < 10; i++) { @@ -17,7 +17,7 @@ test('Iterable#orderBy normal ordering', () => { test('Iterable#orderBy normal ordering with thenBy throws', () => { const xs = [2, 6, 1, 5, 7, 8, 9, 3, 4, 0]; const ys = from(xs) - .pipe(orderBy(x => x)) + .pipe(orderBy((x) => x)) .pipe( thenBy(() => { throw new Error(); @@ -42,7 +42,7 @@ test('Iterable#orderBy selector throws', () => { test('Iterable#orderByDescending normal ordering', () => { const xs = [2, 6, 1, 5, 7, 8, 9, 3, 4, 0]; - const ys = from(xs).pipe(orderByDescending(x => x)); + const ys = from(xs).pipe(orderByDescending((x) => x)); const it = ys[Symbol.iterator](); for (let i = 9; i >= 0; i--) { @@ -55,7 +55,7 @@ test('Iterable#orderByDescending normal ordering', () => { test('Iterable#orderByDescending normal ordering with thenByDescending throws', () => { const xs = [2, 6, 1, 5, 7, 8, 9, 3, 4, 0]; const ys = from(xs) - .pipe(orderByDescending(x => x)) + .pipe(orderByDescending((x) => x)) .pipe( thenByDescending(() => { throw new Error(); diff --git a/spec/iterable-operators/skipwhile-spec.ts b/spec/iterable-operators/skipwhile-spec.ts index ab72663f..db7e7321 100644 --- a/spec/iterable-operators/skipwhile-spec.ts +++ b/spec/iterable-operators/skipwhile-spec.ts @@ -4,7 +4,7 @@ import { from } from 'ix/iterable'; test('Iterable#skipWhile skips some', () => { const xs = [1, 2, 3, 4]; - const ys = from(xs).pipe(skipWhile(x => x < 3)); + const ys = from(xs).pipe(skipWhile((x) => x < 3)); const it = ys[Symbol.iterator](); hasNext(it, 3); @@ -34,7 +34,7 @@ test('Iterable#skipWhile skips all', () => { test('Iterable#skipWhile skips some another run', () => { const xs = [1, 2, 3, 4, 3, 2, 1]; - const ys = from(xs).pipe(skipWhile(x => x < 3)); + const ys = from(xs).pipe(skipWhile((x) => x < 3)); const it = ys[Symbol.iterator](); hasNext(it, 3); diff --git a/spec/iterable-operators/startwith-spec.ts b/spec/iterable-operators/startwith-spec.ts index 0a649968..3bcef971 100644 --- a/spec/iterable-operators/startwith-spec.ts +++ b/spec/iterable-operators/startwith-spec.ts @@ -11,8 +11,6 @@ test('Iterable#startWith adds to beginning', () => { test('Iterable#startWith adds without causing effects', () => { let oops = false; const e = range(1, 5).pipe(tap({ next: () => (oops = true) })); - e.pipe(startWith(0)) - .pipe(take(1)) - .pipe(toArray); + e.pipe(startWith(0)).pipe(take(1)).pipe(toArray); expect(oops).toBeFalsy(); }); diff --git a/spec/iterable-operators/takewhile-spec.ts b/spec/iterable-operators/takewhile-spec.ts index 6f439cc7..572ae440 100644 --- a/spec/iterable-operators/takewhile-spec.ts +++ b/spec/iterable-operators/takewhile-spec.ts @@ -4,7 +4,7 @@ import { from } from 'ix/iterable'; test('Iterable#takeWhile some match', () => { const xs = [1, 2, 3, 4]; - const ys = from(xs).pipe(takeWhile(x => x < 3)); + const ys = from(xs).pipe(takeWhile((x) => x < 3)); const it = ys[Symbol.iterator](); hasNext(it, 1); diff --git a/src/Ix.dom.ts b/src/Ix.dom.ts index 5d2d5980..fbd8cb6e 100644 --- a/src/Ix.dom.ts +++ b/src/Ix.dom.ts @@ -13,5 +13,5 @@ export { ReadableBYOBStreamOptions, ReadableByteStreamOptions } from './asyncite export { fromDOMStream, AsyncIterableReadableStream, - AsyncIterableReadableByteStream + AsyncIterableReadableByteStream, } from './asynciterable/fromdomstream'; diff --git a/src/Ix.node.ts b/src/Ix.node.ts index c7206b6f..18a96075 100644 --- a/src/Ix.node.ts +++ b/src/Ix.node.ts @@ -11,6 +11,7 @@ import './add/asynciterable-operators/buffer'; import './add/asynciterable-operators/catcherror'; import './add/asynciterable-operators/combinelatest'; import './add/asynciterable-operators/concatall'; +import './add/asynciterable-operators/concatmap'; import './add/asynciterable-operators/concat'; import './add/asynciterable-operators/count'; import './add/asynciterable-operators/debounce'; @@ -54,6 +55,7 @@ import './add/asynciterable-operators/orderby'; import './add/asynciterable-operators/pairwise'; import './add/asynciterable-operators/pluck'; import './add/asynciterable-operators/publish'; +import './add/asynciterable-operators/switchmap'; import './add/asynciterable-operators/reduceright'; import './add/asynciterable-operators/reduce'; import './add/asynciterable-operators/repeat'; diff --git a/src/add/asynciterable-operators/concatmap.ts b/src/add/asynciterable-operators/concatmap.ts new file mode 100644 index 00000000..966675d9 --- /dev/null +++ b/src/add/asynciterable-operators/concatmap.ts @@ -0,0 +1,22 @@ +import { AsyncIterableX } from '../../asynciterable/asynciterablex'; +import { concatMap } from '../../asynciterable/operators/concatmap'; +import { FlattenConcurrentSelector } from '../../asynciterable/operators/_flatten'; + +/** + * @ignore + */ +export function concatMapProto( + this: AsyncIterableX, + selector: FlattenConcurrentSelector, + thisArg?: any +) { + return concatMap(selector, thisArg)(this); +} + +AsyncIterableX.prototype.concatMap = concatMapProto; + +declare module '../../asynciterable/asynciterablex' { + interface AsyncIterableX { + concatMap: typeof concatMapProto; + } +} diff --git a/src/add/asynciterable-operators/flat.ts b/src/add/asynciterable-operators/flat.ts index 44214284..24f80fa2 100644 --- a/src/add/asynciterable-operators/flat.ts +++ b/src/add/asynciterable-operators/flat.ts @@ -4,8 +4,8 @@ import { flat } from '../../asynciterable/operators/flat'; /** * @ignore */ -export function flatProto(this: AsyncIterableX, depth?: number): AsyncIterableX { - return flat(depth)(this); +export function flatProto(this: AsyncIterableX, depth: D = -1 as any) { + return flat(depth)(this); } AsyncIterableX.prototype.flat = flatProto; diff --git a/src/add/asynciterable-operators/flatmap.ts b/src/add/asynciterable-operators/flatmap.ts index cff74496..82f1f3bb 100644 --- a/src/add/asynciterable-operators/flatmap.ts +++ b/src/add/asynciterable-operators/flatmap.ts @@ -1,19 +1,16 @@ import { AsyncIterableX } from '../../asynciterable/asynciterablex'; import { flatMap } from '../../asynciterable/operators/flatmap'; +import { FlattenConcurrentSelector } from '../../asynciterable/operators/_flatten'; /** * @ignore */ export function flatMapProto( this: AsyncIterableX, - selector: ( - value: T, - index: number, - signal?: AbortSignal - ) => AsyncIterable | Promise>, + selector: FlattenConcurrentSelector, thisArg?: any -): AsyncIterableX { - return flatMap(selector, thisArg)(this); +) { + return flatMap(selector, thisArg)(this); } AsyncIterableX.prototype.flatMap = flatMapProto; diff --git a/src/add/asynciterable-operators/mergeall.ts b/src/add/asynciterable-operators/mergeall.ts index 5982423d..c45bc156 100644 --- a/src/add/asynciterable-operators/mergeall.ts +++ b/src/add/asynciterable-operators/mergeall.ts @@ -5,7 +5,7 @@ import { mergeAll } from '../../asynciterable/operators/mergeall'; * @ignore */ export function mergeAllProto(this: AsyncIterableX>): AsyncIterableX { - return mergeAll()(this); + return mergeAll()(this); } AsyncIterableX.prototype.mergeAll = mergeAllProto; diff --git a/src/add/asynciterable-operators/orderby.ts b/src/add/asynciterable-operators/orderby.ts index ffdf4849..a5632482 100644 --- a/src/add/asynciterable-operators/orderby.ts +++ b/src/add/asynciterable-operators/orderby.ts @@ -2,11 +2,11 @@ import { AsyncIterableX } from '../../asynciterable/asynciterablex'; import { orderBy, orderByDescending, - OrderedAsyncIterableX + OrderedAsyncIterableX, } from '../../asynciterable/operators/orderby'; import { thenBy as _thenBy, - thenByDescending as _thenByDescending + thenByDescending as _thenByDescending, } from '../../asynciterable/operators/orderby'; /** diff --git a/src/add/asynciterable-operators/switchmap.ts b/src/add/asynciterable-operators/switchmap.ts new file mode 100644 index 00000000..c4d0d74a --- /dev/null +++ b/src/add/asynciterable-operators/switchmap.ts @@ -0,0 +1,21 @@ +import { AsyncIterableX } from '../../asynciterable/asynciterablex'; +import { switchMap } from '../../asynciterable/operators/switchmap'; + +/** + * @ignore + */ +export function switchMapProto>( + this: AsyncIterableX, + selector: (value: T, index: number, signal?: AbortSignal) => R | Promise, + thisArg?: any +) { + return switchMap(selector, thisArg)(this); +} + +AsyncIterableX.prototype.switchMap = switchMapProto; + +declare module '../../asynciterable/asynciterablex' { + interface AsyncIterableX { + switchMap: typeof switchMapProto; + } +} diff --git a/src/add/asynciterable-operators/todomstream.ts b/src/add/asynciterable-operators/todomstream.ts index 37ddbceb..ca1e5907 100644 --- a/src/add/asynciterable-operators/todomstream.ts +++ b/src/add/asynciterable-operators/todomstream.ts @@ -2,7 +2,7 @@ import { AsyncIterableX } from '../../asynciterable/asynciterablex'; import { toDOMStream, ReadableBYOBStreamOptions, - ReadableByteStreamOptions + ReadableByteStreamOptions, } from '../../asynciterable/todomstream'; /** diff --git a/src/add/iterable-operators/orderby.ts b/src/add/iterable-operators/orderby.ts index f759855c..9c33b572 100644 --- a/src/add/iterable-operators/orderby.ts +++ b/src/add/iterable-operators/orderby.ts @@ -2,7 +2,7 @@ import { IterableX } from '../../iterable/iterablex'; import { orderBy, orderByDescending, OrderedIterableX } from '../../iterable/operators/orderby'; import { thenBy as _thenBy, - thenByDescending as _thenByDescending + thenByDescending as _thenByDescending, } from '../../iterable/operators/orderby'; /** diff --git a/src/add/iterable-operators/todomstream.ts b/src/add/iterable-operators/todomstream.ts index b9d20a6c..864e418d 100644 --- a/src/add/iterable-operators/todomstream.ts +++ b/src/add/iterable-operators/todomstream.ts @@ -2,7 +2,7 @@ import { IterableX } from '../../iterable/iterablex'; import { toDOMStream } from '../../iterable/todomstream'; import { ReadableBYOBStreamOptions, - ReadableByteStreamOptions + ReadableByteStreamOptions, } from '../../asynciterable/todomstream'; /** diff --git a/src/asynciterable/from.ts b/src/asynciterable/from.ts index 9db1b245..d9f51f83 100644 --- a/src/asynciterable/from.ts +++ b/src/asynciterable/from.ts @@ -7,7 +7,7 @@ import { isArrayLike, isIterator, isPromise, - isObservable + isObservable, } from '../util/isiterable'; import { Observable } from '../observer'; import { toLength } from '../util/tolength'; @@ -42,7 +42,7 @@ export let FromObservableAsyncIterable: new ( export function _initialize(Ctor: typeof AsyncIterableX) { /** @nocollapse */ - from = function ( + from = function ( source: AsyncIterableInput, selector: (value: TSource, index: number) => TResult | Promise = identityAsync, thisArg?: any @@ -82,7 +82,7 @@ export function _initialize(Ctor: typeof AsyncIterableX) { async *[Symbol.asyncIterator]() { let i = 0; - const length = toLength((> this._source).length); + const length = toLength((>this._source).length); while (i < length) { yield await this._selector(this._source[i], i++); } @@ -105,7 +105,7 @@ export function _initialize(Ctor: typeof AsyncIterableX) { async *[Symbol.asyncIterator]() { let i = 0; - for await (const item of > this._source) { + for await (const item of >this._source) { yield await this._selector(item, i++); } } @@ -113,7 +113,7 @@ export function _initialize(Ctor: typeof AsyncIterableX) { // eslint-disable-next-line no-shadow FromPromiseIterable = class FromPromiseIterable extends Ctor< - TResult + TResult > { private _source: PromiseLike; private _selector: (value: TSource, index: number) => TResult | Promise; @@ -163,7 +163,7 @@ export function _initialize(Ctor: typeof AsyncIterableX) { }, complete() { sink.end(); - } + }, }); function onAbort() { diff --git a/src/asynciterable/of.ts b/src/asynciterable/of.ts index 363b8481..2d6d391c 100644 --- a/src/asynciterable/of.ts +++ b/src/asynciterable/of.ts @@ -24,6 +24,8 @@ export class OfAsyncIterable extends AsyncIterableX { * @param {...TSource[]} args The elements to turn into an async-iterable sequence. * @returns {AsyncIterableX} The async-iterable sequence created from the elements. */ -export function of(...args: TSource[]): AsyncIterableX { - return new OfAsyncIterable(args); +export function of( + ...args: TSource +): AsyncIterableX { + return new OfAsyncIterable(args); } diff --git a/src/asynciterable/operators/_flatten.ts b/src/asynciterable/operators/_flatten.ts new file mode 100644 index 00000000..9293b364 --- /dev/null +++ b/src/asynciterable/operators/_flatten.ts @@ -0,0 +1,173 @@ +import { AsyncIterableInput, AsyncIterableX } from '../asynciterablex'; +import { wrapWithAbort } from '../operators/withabort'; +import { AbortError, throwIfAborted } from '../../aborterror'; +import { safeRace } from '../../util/safeRace'; +import { isPromise } from '../../util/isiterable'; +import { as as asAsyncIterable } from '../as'; + +export type FlattenConcurrentSelector = ( + value: TSource, + index: number, + signal?: AbortSignal +) => Promise> | AsyncIterableInput; + +const NEVER_PROMISE = new Promise>(() => {}); + +const enum Type { + OUTER = 0, + INNER = 1, +} + +function ignoreInnerAbortErrors(signal: AbortSignal) { + return function ignoreInnerAbortError(e?: any) { + if (signal.aborted && e instanceof AbortError) { + return NEVER_PROMISE; + } + throw e; + }; +} + +async function* wrapIterator( + source: AsyncIterable, + index: number, + type: Type, + signal?: AbortSignal +) { + for await (const value of wrapWithAbort(source, signal)) { + throwIfAborted(signal); + yield { type, index, value }; + } + return { type, index, value: undefined }; +} + +export class FlattenConcurrentAsyncIterable extends AsyncIterableX { + constructor( + private _source: AsyncIterable, + private _selector: FlattenConcurrentSelector, + private _concurrent: number, + private _switchMode: boolean, + private _thisArg?: any + ) { + super(); + this._concurrent = this._switchMode ? 1 : Math.max(_concurrent, 0); + } + async *[Symbol.asyncIterator](outerSignal?: AbortSignal) { + throwIfAborted(outerSignal); + + type OuterWrapper = { value: TSource; index: number; type: Type.OUTER }; + type InnerWrapper = { value: TResult; index: number; type: Type.INNER }; + + let active = 0; + let outerIndex = 0; + let outerComplete = false; + + const thisArg = this._thisArg; + const selector = this._selector; + const switchMode = this._switchMode; + const concurrent = this._concurrent; + + const outerValues = new Array(0); + const innerIndices = new Array(0); + const controllers = new Array(isFinite(concurrent) ? concurrent : 0); + const inners = new Array>(isFinite(concurrent) ? concurrent : 0); + + const outer = wrapIterator(this._source, 0, Type.OUTER, outerSignal) as AsyncGenerator< + OuterWrapper + >; + const results = [outer.next()] as Promise>[]; + + try { + while (1) { + const { + done = false, + value: { type, value, index }, + } = await safeRace(results); + + if (!done) { + switch (type) { + case Type.OUTER: { + if (switchMode) { + active = 0; + } + if (active < concurrent) { + pullNextOuter(value as TSource); + } else { + outerValues.push(value as TSource); + } + results[0] = outer.next(); + break; + } + case Type.INNER: { + yield value as TResult; + results[index] = pullNextInner(index); + break; + } + } + } else { + // ignore this result slot + results[index] = NEVER_PROMISE; + switch (type) { + case Type.OUTER: + outerComplete = true; + break; + case Type.INNER: + --active; + // return the current slot to the pool + innerIndices.push(index); + // synchronously drain the `outerValues` buffer + while (active < concurrent && outerValues.length) { + // Don't use `await` so we avoid blocking while the number of active inner sequences is less than `concurrent`. + pullNextOuter(outerValues.shift()!); + } + break; + } + if (outerComplete && active + outerValues.length === 0) { + return; + } + } + } + } finally { + controllers.forEach((controller) => { + controller?.abort(); + }); + } + + function pullNextInner(index: number) { + const result = inners[index - 1].next(); + const { [index - 1]: controller } = controllers; + return result.catch(ignoreInnerAbortErrors(controller.signal)); + } + + function pullNextOuter(outerValue: TSource) { + ++active; + + const index = innerIndices.pop() || active; + + // abort the current inner iterator first + if (switchMode && controllers[index - 1]) { + controllers[index - 1].abort(); + } + + controllers[index - 1] = new AbortController(); + const innerSignal = controllers[index - 1].signal; + + // Get the next inner sequence. + // `selector` is a sync or async function that returns AsyncIterableInput. + const inner = selector.call(thisArg, outerValue, outerIndex++, innerSignal); + + const wrapAndPullInner = (inner: AsyncIterableInput | TResult) => { + inners[index - 1] = wrapIterator( + asAsyncIterable(inner), + index, + Type.INNER, + innerSignal + ) as AsyncGenerator; + return pullNextInner(index); + }; + + results[index] = isPromise(inner) + ? (inner.then(wrapAndPullInner) as Promise>) + : wrapAndPullInner(inner); + } + } +} diff --git a/src/asynciterable/operators/concatmap.ts b/src/asynciterable/operators/concatmap.ts new file mode 100644 index 00000000..ea7831fb --- /dev/null +++ b/src/asynciterable/operators/concatmap.ts @@ -0,0 +1,26 @@ +import { FlattenConcurrentSelector, FlattenConcurrentAsyncIterable } from './_flatten'; +import { OperatorAsyncFunction } from '../../interfaces'; + +/** + * Projects each element of an async-iterable sequence to an async-iterable sequence and merges + * the resulting async-iterable sequences into one async-iterable sequence. + * + * @template TSource The type of the elements in the source sequence. + * @template TResult The type of the elements in the projected inner sequences and the elements in the merged result sequence. + * @param {(( + * value: TSource, + * index: number, + * signal?: AbortSignal + * ) => AsyncIterable | Promise>)} selector A transform function to apply to each element. + * @param {*} [thisArg] Option this for binding to the selector. + * @returns {OperatorAsyncFunction} An operator that creates an async-iterable sequence whose + * elements are the result of invoking the one-to-many transform function on each element of the input sequence. + */ +export function concatMap( + selector: FlattenConcurrentSelector, + thisArg?: any +): OperatorAsyncFunction { + return function concatMapOperatorFunction(source) { + return new FlattenConcurrentAsyncIterable(source, selector, 1, false, thisArg); + }; +} diff --git a/src/asynciterable/operators/flat.ts b/src/asynciterable/operators/flat.ts index 2a20729c..f411bedb 100644 --- a/src/asynciterable/operators/flat.ts +++ b/src/asynciterable/operators/flat.ts @@ -1,47 +1,24 @@ +import { flatMap } from './flatmap'; import { AsyncIterableX } from '../asynciterablex'; import { isAsyncIterable } from '../../util/isiterable'; -import { MonoTypeOperatorAsyncFunction } from '../../interfaces'; -import { wrapWithAbort } from './withabort'; -import { throwIfAborted } from '../../aborterror'; -export class FlattenAsyncIterable extends AsyncIterableX { - private _source: AsyncIterable; - private _depth: number; +type Flattened = Depth extends -1 + ? FlattenInfinite + : FlattenWithDepth; - constructor(source: AsyncIterable, depth: number) { - super(); - this._source = source; - this._depth = depth; - } +type FlattenInfinite = Arr extends AsyncIterable + ? FlattenInfinite + : Arr; - // eslint-disable-next-line consistent-return - private async *_flatten( - source: AsyncIterable, - depth: number, - signal?: AbortSignal - ): AsyncIterable { - if (depth === 0) { - for await (const item of wrapWithAbort(source, signal)) { - yield item; - } - return undefined; - } - for await (const item of wrapWithAbort(source, signal)) { - if (isAsyncIterable(item)) { - for await (const innerItem of this._flatten(item, depth - 1, signal)) { - yield innerItem; - } - } else { - yield item; - } - } - } - - [Symbol.asyncIterator](signal?: AbortSignal) { - throwIfAborted(signal); - return this._flatten(this._source, this._depth, signal)[Symbol.asyncIterator](); - } -} +type FlattenWithDepth = { + done: Arr; + recur: Arr extends AsyncIterable + ? FlattenWithDepth< + T, + [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth] + > + : Arr; +}[Depth extends -1 ? 'done' : 'recur']; /** * Flattens the nested async-iterable by the given depth. @@ -50,8 +27,16 @@ export class FlattenAsyncIterable extends AsyncIterableX { * @param {number} [depth=Infinity] The depth to flatten the async-iterable sequence if specified, otherwise infinite. * @returns {MonoTypeOperatorAsyncFunction} An operator that flattens the async-iterable sequence. */ -export function flat(depth = Infinity): MonoTypeOperatorAsyncFunction { - return function flattenOperatorFunction(source: AsyncIterable): AsyncIterableX { - return new FlattenAsyncIterable(source, depth); +export function flat(depth: D = -1 as any, concurrent = Infinity) { + depth = (depth < 0 ? Infinity : depth) as any; + return function flattenOperatorFunction( + source: AsyncIterable + ): AsyncIterableX> { + return flatMap((item: any) => { + if (isAsyncIterable(item)) { + return depth > 0 ? flat(depth - 1)(item) : item; + } + return [item]; + }, concurrent)(source) as AsyncIterableX>; }; } diff --git a/src/asynciterable/operators/flatmap.ts b/src/asynciterable/operators/flatmap.ts index 36c58ada..991970c3 100644 --- a/src/asynciterable/operators/flatmap.ts +++ b/src/asynciterable/operators/flatmap.ts @@ -1,44 +1,5 @@ -import { AsyncIterableX } from '../asynciterablex'; +import { FlattenConcurrentSelector, FlattenConcurrentAsyncIterable } from './_flatten'; import { OperatorAsyncFunction } from '../../interfaces'; -import { wrapWithAbort } from './withabort'; -import { throwIfAborted } from '../../aborterror'; - -export class FlatMapAsyncIterable extends AsyncIterableX { - private _source: AsyncIterable; - private _selector: ( - value: TSource, - index: number, - signal?: AbortSignal - ) => AsyncIterable | Promise>; - private _thisArg?: any; - - constructor( - source: AsyncIterable, - selector: ( - value: TSource, - index: number, - signal?: AbortSignal - ) => AsyncIterable | Promise>, - thisArg?: any - ) { - super(); - this._source = source; - this._selector = selector; - this._thisArg = thisArg; - } - - async *[Symbol.asyncIterator](signal?: AbortSignal) { - throwIfAborted(signal); - const { _source: source, _selector: selector, _thisArg: thisArg } = this; - let index = 0; - for await (const outer of wrapWithAbort(source, signal)) { - const inners = await selector.call(thisArg, outer, index++, signal); - for await (const inner of wrapWithAbort(inners, signal)) { - yield inner; - } - } - } -} /** * Projects each element of an async-iterable sequence to an async-iterable sequence and merges @@ -56,14 +17,11 @@ export class FlatMapAsyncIterable extends AsyncIterableX( - selector: ( - value: TSource, - index: number, - signal?: AbortSignal - ) => AsyncIterable | Promise>, + selector: FlattenConcurrentSelector, + concurrent = Infinity, thisArg?: any ): OperatorAsyncFunction { - return function flatMapOperatorFunction(source: AsyncIterable): AsyncIterableX { - return new FlatMapAsyncIterable(source, selector, thisArg); + return function flatMapOperatorFunction(source) { + return new FlattenConcurrentAsyncIterable(source, selector, concurrent, false, thisArg); }; } diff --git a/src/asynciterable/operators/index.ts b/src/asynciterable/operators/index.ts index f36cc7df..e241650d 100644 --- a/src/asynciterable/operators/index.ts +++ b/src/asynciterable/operators/index.ts @@ -4,6 +4,7 @@ export * from './buffercountortime'; export * from './catcherror'; export * from './combinelatestwith'; export * from './concatall'; +export * from './concatmap'; export * from './concatwith'; export * from './debounce'; export * from './defaultifempty'; @@ -45,6 +46,7 @@ export * from './skipuntil'; export * from './skipwhile'; export * from './slice'; export * from './startwith'; +export * from './switchmap'; export * from './takelast'; export * from './take'; export * from './takeuntil'; diff --git a/src/asynciterable/operators/mergeall.ts b/src/asynciterable/operators/mergeall.ts index ce4d83a8..22f0fc27 100644 --- a/src/asynciterable/operators/mergeall.ts +++ b/src/asynciterable/operators/mergeall.ts @@ -1,7 +1,5 @@ -import { AsyncIterableX } from '../asynciterablex'; import { as } from '../as'; import { flatMap } from './flatmap'; -import { OperatorAsyncFunction } from '../../interfaces'; /** * Merges elements from all inner async-iterable sequences into a single async-iterable sequence. @@ -9,10 +7,8 @@ import { OperatorAsyncFunction } from '../../interfaces'; * @template TSource The type of the elements in the source sequences. * @returns {OperatorAsyncFunction, TSource>} The async-iterable sequence that merges the elements of the inner sequences. */ -export function mergeAll(): OperatorAsyncFunction, TSource> { - return function mergeAllOperatorFunction( - source: AsyncIterable> - ): AsyncIterableX { - return as(source)['pipe'](flatMap, TSource>((s) => s)); +export function mergeAll(concurrent = Infinity) { + return function mergeAllOperatorFunction(source: AsyncIterable>) { + return as(source)['pipe'](flatMap((s) => s, concurrent)); }; } diff --git a/src/asynciterable/operators/switchmap.ts b/src/asynciterable/operators/switchmap.ts new file mode 100644 index 00000000..babd3dd0 --- /dev/null +++ b/src/asynciterable/operators/switchmap.ts @@ -0,0 +1,26 @@ +import { FlattenConcurrentSelector, FlattenConcurrentAsyncIterable } from './_flatten'; +import { OperatorAsyncFunction } from '../../interfaces'; + +/** + * Projects each element of an async-iterable sequence to an async-iterable sequence, + * emitting values only from the most recently projected async-iterable sequence. + * + * @template TSource The type of the elements in the source sequence. + * @template TResult The type of the elements in the projected inner sequences and the elements in the merged result sequence. + * @param {(( + * value: TSource, + * index: number, + * signal?: AbortSignal + * ) => AsyncIterableInput)} selector A transform function to apply to each element. + * @param {*} [thisArg] Option this for binding to the selector. + * @returns {OperatorAsyncFunction} An operator that creates an async-iterable sequence whose + * elements are the result of invoking the one-to-many transform function on each element of the input sequence. + */ +export function switchMap( + selector: FlattenConcurrentSelector, + thisArg?: any +): OperatorAsyncFunction { + return function switchMapOperatorFunction(source) { + return new FlattenConcurrentAsyncIterable(source, selector, 1, true, thisArg); + }; +} diff --git a/src/asynciterable/operators/todomstream.ts b/src/asynciterable/operators/todomstream.ts index 7cb5eef9..c56d80fc 100644 --- a/src/asynciterable/operators/todomstream.ts +++ b/src/asynciterable/operators/todomstream.ts @@ -1,7 +1,7 @@ import { toDOMStream as toDOMStreamOperator, ReadableBYOBStreamOptions, - ReadableByteStreamOptions + ReadableByteStreamOptions, } from '../../asynciterable/todomstream'; import { UnaryFunction } from '../../interfaces'; @@ -22,8 +22,9 @@ export function toDOMStream( if (!options || !('type' in options) || options['type'] !== 'bytes') { return toDOMStreamOperator(source, options as QueuingStrategy | undefined); } - return toDOMStreamOperator(source, options as - | ReadableBYOBStreamOptions - | ReadableByteStreamOptions); + return toDOMStreamOperator( + source, + options as ReadableBYOBStreamOptions | ReadableByteStreamOptions + ); }; } diff --git a/src/asynciterable/operators/tonodestream.ts b/src/asynciterable/operators/tonodestream.ts index 0eee6852..6cc3789a 100644 --- a/src/asynciterable/operators/tonodestream.ts +++ b/src/asynciterable/operators/tonodestream.ts @@ -3,8 +3,8 @@ import { AsyncIterableReadable } from '../tonodestream'; import { BufferLike, UnaryFunction } from '../../interfaces'; export function toNodeStream(): UnaryFunction< -AsyncIterable, -AsyncIterableReadable + AsyncIterable, + AsyncIterableReadable >; export function toNodeStream( options: ReadableOptions & { objectMode: true } diff --git a/src/iterable/from.ts b/src/iterable/from.ts index 5a30a667..338f647f 100644 --- a/src/iterable/from.ts +++ b/src/iterable/from.ts @@ -19,7 +19,7 @@ export let FromIterable: new ( export function _initialize(Ctor: typeof IterableX) { /** @nocollapse */ - from = function ( + from = function ( source: Iterable | Iterator | ArrayLike, selector: (value: TSource, index: number) => TResult = identity, thisArg?: any @@ -55,13 +55,13 @@ export function _initialize(Ctor: typeof IterableX) { const iterable = isIterable(this._source); let i = 0; if (iterable) { - for (const item of > this._source) { + for (const item of >this._source) { yield this._fn(item, i++); } } else { - const length = toLength((> this._source).length); + const length = toLength((>this._source).length); while (i < length) { - const val = (> this._source)[i]; + const val = (>this._source)[i]; yield this._fn(val, i++); } } diff --git a/src/iterable/operators/todomstream.ts b/src/iterable/operators/todomstream.ts index f4ad7245..3e26a7ea 100644 --- a/src/iterable/operators/todomstream.ts +++ b/src/iterable/operators/todomstream.ts @@ -1,7 +1,7 @@ import { toDOMStream as toDOMStreamOperator } from '../../iterable/todomstream'; import { ReadableBYOBStreamOptions, - ReadableByteStreamOptions + ReadableByteStreamOptions, } from '../../asynciterable/todomstream'; import { UnaryFunction } from '../../interfaces'; @@ -22,8 +22,9 @@ export function toDOMStream( if (!options || !('type' in options) || options['type'] !== 'bytes') { return toDOMStreamOperator(source, options as QueuingStrategy | undefined); } - return toDOMStreamOperator(source, options as - | ReadableBYOBStreamOptions - | ReadableByteStreamOptions); + return toDOMStreamOperator( + source, + options as ReadableBYOBStreamOptions | ReadableByteStreamOptions + ); }; } diff --git a/src/iterable/operators/tonodestream.ts b/src/iterable/operators/tonodestream.ts index 49e7209b..1789e073 100644 --- a/src/iterable/operators/tonodestream.ts +++ b/src/iterable/operators/tonodestream.ts @@ -3,8 +3,8 @@ import { IterableReadable } from '../../iterable/tonodestream'; import { BufferLike, UnaryFunction } from '../../interfaces'; export function toNodeStream(): UnaryFunction< -Iterable, -IterableReadable + Iterable, + IterableReadable >; export function toNodeStream( options: ReadableOptions & { objectMode: true } diff --git a/src/scheduler.ts b/src/scheduler.ts index 3d97b0f9..98c011b4 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -28,7 +28,7 @@ export class DefaultScheduler implements Scheduler { } delay(dueTime: number) { - return new Promise(res => setTimeout(res, dueTime)); + return new Promise((res) => setTimeout(res, dueTime)); } schedule(action: () => void, dueTime: number): Subscription { diff --git a/src/util/bindcallback.ts b/src/util/bindcallback.ts index aae0e274..f977156c 100644 --- a/src/util/bindcallback.ts +++ b/src/util/bindcallback.ts @@ -6,25 +6,25 @@ export function bindCallback(func: any, thisArg: any, argCount: number) { return func; } switch (argCount) { - case 0: - return function () { - return func.call(thisArg); - }; - case 1: - return function (arg: any) { - return func.call(thisArg, arg); - }; - case 2: - return function (value: any, index: number) { - return func.call(thisArg, value, index); - }; - case 3: - return function (value: any, index: number, collection: any[]) { - return func.call(thisArg, value, index, collection); - }; - default: - return function () { - return func.apply(thisArg, arguments); - }; + case 0: + return function () { + return func.call(thisArg); + }; + case 1: + return function (arg: any) { + return func.call(thisArg, arg); + }; + case 2: + return function (value: any, index: number) { + return func.call(thisArg, value, index); + }; + case 3: + return function (value: any, index: number, collection: any[]) { + return func.call(thisArg, value, index, collection); + }; + default: + return function () { + return func.apply(thisArg, arguments); + }; } } diff --git a/src/util/toobserver.ts b/src/util/toobserver.ts index 596052db..3602f7dd 100644 --- a/src/util/toobserver.ts +++ b/src/util/toobserver.ts @@ -15,15 +15,15 @@ export function toObserver( if (observer && typeof observer === 'object') { return { - next: isFunction(observer.next) ? x => observer.next!(x) : noop, - error: isFunction(observer.error) ? e => observer.error!(e) : noop, - complete: isFunction(observer.complete) ? () => observer.complete!() : noop + next: isFunction(observer.next) ? (x) => observer.next!(x) : noop, + error: isFunction(observer.error) ? (e) => observer.error!(e) : noop, + complete: isFunction(observer.complete) ? () => observer.complete!() : noop, }; } return { next: isFunction(next) ? next : noop, error: isFunction(error) ? error : noop, - complete: isFunction(complete) ? complete : noop + complete: isFunction(complete) ? complete : noop, }; }