From 2aa1432b3a28574c9a05aa7bc9f5168df75f8c26 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sat, 2 Sep 2023 11:21:01 +1000 Subject: [PATCH 01/33] feat: add experimental typescript types --- main.d.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 24 +++++++++++++++++++----- package.json | 5 ++++- 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 main.d.ts diff --git a/main.d.ts b/main.d.ts new file mode 100644 index 0000000..09fb0f6 --- /dev/null +++ b/main.d.ts @@ -0,0 +1,46 @@ +import type { UnionToIntersection } from 'type-fest' + +interface CoreOptions { + depth: number + overrides: object + customiser: string + configAlias: string + freezeConfig: boolean + extensions: boolean +} + +interface ExtensionOptions { + globalThis: object + publicPrefix: string + privatePrefix: string + functionAlias: object + moduleAlias: string[] +} + +type Options = CoreOptions & ExtensionOptions + +type Composed = (...args: any) => any +type Composable = (deps: Modules) => (...args: any) => any +type Module = Record +type Modules = Record + +type ComposedModule = { + [K in keyof T]: ReturnType +} + +type ModuleParameters = + Key extends PropertyKey ? Parameters[0] : never + +type ModuleDependencies = UnionToIntersection>> + +type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> + +interface Asis { + asis(path: Path, opts?: Partial): Record +} + +interface Composer { + compose: Compose & Asis +} + +export default function composer(config: T): Composer \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 202a928..831c029 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "": { "name": "module-composer", "version": "0.150.0", + "dev": true, "license": "ISC", "dependencies": { "flat": "^5.0.2", @@ -26,7 +27,8 @@ "node-sloc": "^0.2.1", "npm-check-updates": "^16.13.2", "task-library": "^0.269.0", - "testing": "file:./testing" + "testing": "file:./testing", + "type-fest": "^4.3.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2870,6 +2872,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.3", "dev": true, @@ -6533,12 +6547,12 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.3.1.tgz", + "integrity": "sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 310c09c..9bd638c 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "files": [ "core.js", "main.js", + "main.d.ts", "src/**/*", "extensions/*" ], "main": "./main.js", + "types": "./main.d.ts", "scripts": { "cov": "npx task cov", "deploy": "npx task deploy", @@ -47,7 +49,8 @@ "node-sloc": "^0.2.1", "npm-check-updates": "^16.13.2", "task-library": "^0.269.0", - "testing": "file:./testing" + "testing": "file:./testing", + "type-fest": "^4.3.1" }, "keywords": [ "application-architecture", From a9ee149cef18381c5be13f91bf30f09ec35395c8 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 10:21:33 +1000 Subject: [PATCH 02/33] chore: add a few tests for the types --- main.d.ts | 4 +- main.test-d.ts | 23 ++ package-lock.json | 900 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 4 files changed, 925 insertions(+), 6 deletions(-) create mode 100644 main.test-d.ts diff --git a/main.d.ts b/main.d.ts index 09fb0f6..6f8234a 100644 --- a/main.d.ts +++ b/main.d.ts @@ -43,4 +43,6 @@ interface Composer { compose: Compose & Asis } -export default function composer(config: T): Composer \ No newline at end of file +declare function composer(config: T): Composer + +export default composer; diff --git a/main.test-d.ts b/main.test-d.ts new file mode 100644 index 0000000..019e4db --- /dev/null +++ b/main.test-d.ts @@ -0,0 +1,23 @@ +import { expectType } from 'tsd'; + +import composer from './main'; + +type BarDeps = { + foo: { doAThing: () => void } +} + +const modules = { + foo: { doAThing: () => () => {} }, + bar: { doAThing: ({ foo }: BarDeps) => () => 1 } +} +const { compose } = composer(modules); + +// compose with no dependencies +expectType<{ foo: { doAThing: () => void }}>(compose('foo', {})); + +// compose with a dependency +const { foo } = compose('foo', {}) +expectType<{ bar: { doAThing: () => number }}>(compose('bar', { foo })); + +// compose as-is with no dependencies +expectType<{ foo: { doAThing: () => () => void } }>(compose.asis('foo')); diff --git a/package-lock.json b/package-lock.json index 831c029..66c228c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "npm-check-updates": "^16.13.2", "task-library": "^0.269.0", "testing": "file:./testing", + "tsd": "^0.29.0", "type-fest": "^4.3.1" } }, @@ -40,6 +41,184 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", + "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -211,6 +390,18 @@ "node": ">=8" } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", @@ -531,6 +722,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@sindresorhus/is": { "version": "5.3.0", "dev": true, @@ -582,6 +779,15 @@ "node": ">= 10" } }, + "node_modules/@tsd/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-VtjHPAKJqLJoHHKBDNofzvQB2+ZVxjXU/Gw6INAS9aINLQYVsxfzrQ2s84huCeYWZRTtrr7R0J7XgpZHjNwBCw==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@tufjs/canonical-json": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", @@ -639,6 +845,22 @@ "@types/responselike": "*" } }, + "node_modules/@types/eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "dev": true, @@ -655,6 +877,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -677,11 +905,23 @@ "@types/unist": "*" } }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "node_modules/@types/node": { "version": "18.7.13", "dev": true, "license": "MIT" }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, "node_modules/@types/responselike": { "version": "1.0.0", "dev": true, @@ -791,6 +1031,33 @@ "string-width": "^4.1.0" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -940,6 +1207,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/async": { "version": "3.2.4", "dev": true, @@ -1278,6 +1554,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ccount": { "version": "1.1.0", "dev": true, @@ -1815,6 +2126,31 @@ "node": ">=0.10.0" } }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "dev": true, @@ -1883,6 +2219,15 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "dev": true, @@ -2064,6 +2409,15 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.21.2", "dev": true, @@ -2233,6 +2587,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-formatter-pretty": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz", + "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==", + "dev": true, + "dependencies": { + "@types/eslint": "^7.2.13", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "eslint-rule-docs": "^1.1.5", + "log-symbols": "^4.0.0", + "plur": "^4.0.0", + "string-width": "^4.2.0", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.7", "dev": true, @@ -2327,6 +2703,12 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-rule-docs": { + "version": "1.1.235", + "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz", + "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==", + "dev": true + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2964,6 +3346,15 @@ "dev": true, "license": "MIT" }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/has": { "version": "1.0.3", "dev": true, @@ -3297,6 +3688,15 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "node_modules/irregular-plurals": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-alphabetical": { "version": "1.0.4", "dev": true, @@ -3332,6 +3732,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "node_modules/is-bigint": { "version": "1.0.4", "dev": true, @@ -3650,6 +4056,18 @@ "dev": true, "license": "MIT" }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "dev": true, @@ -3744,11 +4162,41 @@ "node": ">=10" } }, + "node_modules/jest-diff": { + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", + "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jju": { "version": "1.4.0", "dev": true, "license": "MIT" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "dev": true, @@ -3828,12 +4276,21 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "4.1.5", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/latest-version": { @@ -3863,6 +4320,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, @@ -3901,6 +4364,22 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/longest-streak": { "version": "2.0.4", "dev": true, @@ -3997,6 +4476,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/markdown-table": { "version": "2.0.0", "dev": true, @@ -4157,6 +4648,107 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -4302,6 +4894,15 @@ "node": ">=4" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "dev": true, @@ -4321,6 +4922,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -5390,6 +6014,30 @@ "node": ">=0.10.0" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -5463,6 +6111,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/plur": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz", + "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==", + "dev": true, + "dependencies": { + "irregular-plurals": "^3.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5472,6 +6135,32 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/proc-log": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", @@ -5626,6 +6315,12 @@ "node": ">=0.10.0" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/read-package-json": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", @@ -5654,6 +6349,135 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5679,6 +6503,19 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "dev": true, @@ -6337,6 +7174,18 @@ "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6360,6 +7209,19 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "dev": true, @@ -6487,6 +7349,15 @@ "dev": true, "license": "MIT" }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/trough": { "version": "1.0.5", "dev": true, @@ -6520,6 +7391,27 @@ "json5": "lib/cli.js" } }, + "node_modules/tsd": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.29.0.tgz", + "integrity": "sha512-5B7jbTj+XLMg6rb9sXRBGwzv7h8KJlGOkTHxY63eWpZJiQ5vJbXEjL0u7JkIxwi5EsrRE1kRVUWmy6buK/ii8A==", + "dev": true, + "dependencies": { + "@tsd/typescript": "~5.2.2", + "eslint-formatter-pretty": "^4.1.0", + "globby": "^11.0.1", + "jest-diff": "^29.0.3", + "meow": "^9.0.0", + "path-exists": "^4.0.0", + "read-pkg-up": "^7.0.0" + }, + "bin": { + "tsd": "dist/cli.js" + }, + "engines": { + "node": ">=14.16" + } + }, "node_modules/tuf-js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", diff --git a/package.json b/package.json index 9bd638c..6c5b0c4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "pre": "npx task pre", "setup": "npm i && npx task setup", "start": "npx task start", - "test": "npx task test" + "test": "npx task test", + "tsd": "npx tsd" }, "dependencies": { "flat": "^5.0.2", @@ -50,6 +51,7 @@ "npm-check-updates": "^16.13.2", "task-library": "^0.269.0", "testing": "file:./testing", + "tsd": "^0.29.0", "type-fest": "^4.3.1" }, "keywords": [ From 87afd60f9464aa5f9c4ae2eb22ca0928d0c92ea0 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 14:09:27 +1000 Subject: [PATCH 03/33] chore: add linting for ts files --- .eslintrc.json | 13 +- main.d.ts | 7 +- main.test-d.ts | 9 +- package-lock.json | 314 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 + 5 files changed, 337 insertions(+), 11 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index a0f422f..5dd08fe 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -45,6 +45,17 @@ "off" ] }, - "overrides": [], + "overrides": [{ + "files": [ + "main.d.ts", + "main.test-d.ts" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"] + }], "plugins": [] } diff --git a/main.d.ts b/main.d.ts index 6f8234a..fffbe7a 100644 --- a/main.d.ts +++ b/main.d.ts @@ -1,4 +1,4 @@ -import type { UnionToIntersection } from 'type-fest' +import type { UnionToIntersection } from 'type-fest'; interface CoreOptions { depth: number @@ -19,8 +19,9 @@ interface ExtensionOptions { type Options = CoreOptions & ExtensionOptions -type Composed = (...args: any) => any -type Composable = (deps: Modules) => (...args: any) => any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Composed = (...args: any[]) => unknown +type Composable = (deps: Modules) => Composed type Module = Record type Modules = Record diff --git a/main.test-d.ts b/main.test-d.ts index 019e4db..600ed7a 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -1,4 +1,4 @@ -import { expectType } from 'tsd'; +import { expectType } from 'tsd/dist/index'; import composer from './main'; @@ -8,15 +8,18 @@ type BarDeps = { const modules = { foo: { doAThing: () => () => {} }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars bar: { doAThing: ({ foo }: BarDeps) => () => 1 } -} +}; + const { compose } = composer(modules); // compose with no dependencies expectType<{ foo: { doAThing: () => void }}>(compose('foo', {})); // compose with a dependency -const { foo } = compose('foo', {}) +const { foo } = compose('foo', {}); expectType<{ bar: { doAThing: () => number }}>(compose('bar', { foo })); // compose as-is with no dependencies diff --git a/package-lock.json b/package-lock.json index 66c228c..862ae79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,14 @@ "": { "name": "module-composer", "version": "0.150.0", - "dev": true, "license": "ISC", "dependencies": { "flat": "^5.0.2", "lodash": "^4.17.21" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", "cloc": "^2.11.0", "doctoc": "^2.2.1", "ejs": "^3.1.9", @@ -29,6 +30,9 @@ "task-library": "^0.269.0", "testing": "file:./testing", "tsd": "^0.29.0", + "typescript": "^5.2.2" + }, + "peerDependencies": { "type-fest": "^4.3.1" } }, @@ -930,11 +934,287 @@ "@types/node": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.6", "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", + "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/type-utils": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", + "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", + "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3563,9 +3843,10 @@ } }, "node_modules/ignore": { - "version": "5.2.0", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -7367,6 +7648,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-api-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", + "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -7442,7 +7735,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.3.1.tgz", "integrity": "sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==", - "dev": true, + "peer": true, "engines": { "node": ">=16" }, @@ -7471,6 +7764,19 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "dev": true, diff --git a/package.json b/package.json index 6c5b0c4..5bdb65d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "lodash": "^4.17.21" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", "cloc": "^2.11.0", "doctoc": "^2.2.1", "ejs": "^3.1.9", @@ -52,6 +54,9 @@ "task-library": "^0.269.0", "testing": "file:./testing", "tsd": "^0.29.0", + "typescript": "^5.2.2" + }, + "peerDependencies": { "type-fest": "^4.3.1" }, "keywords": [ From 03f04a71fa10f399f96357a37ce8af6e450afd52 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 15:05:38 +1000 Subject: [PATCH 04/33] chore: extended the test scenarios for the types --- main.test-d.ts | 76 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/main.test-d.ts b/main.test-d.ts index 600ed7a..5ef7460 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -2,25 +2,81 @@ import { expectType } from 'tsd/dist/index'; import composer from './main'; -type BarDeps = { - foo: { doAThing: () => void } +type Toy = string +type Food = string + +type ServeFood = (food: Food, bowl: Food[]) => void +type FillBowl = (choice: Food, bowl: Food[]) => void +type RandomToy = () => Toy +type Play = () => void +type Eat = () => void + +type HumanDeps = { + food: { serve: ServeFood } +} + +type PlayDeps = { + toys: { randomToy: () => Toy } + util: { shuffle: (str: string) => string} +} + +type EatDeps = { + human: { fillBowl: FillBowl } } const modules = { - foo: { doAThing: () => () => {} }, + util : { + shuffle: (str: string) => [...str].sort(() => Math.random() - 0.5).join('') + }, + + food: { + serve: (): ServeFood => (food, bowl) => { bowl.push(food); } + }, + + human: { + fillBowl: ({ food }: HumanDeps): FillBowl => (choice, bowl) => food.serve(choice, bowl) + }, + + toys: { + randomToy: (): RandomToy => () => 'yarn' + }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - bar: { doAThing: ({ foo }: BarDeps) => () => 1 } + cat: { + play: ({ toys, util }: PlayDeps): Play => () => { + const toy = toys.randomToy(); + util.shuffle(toy); + } , + + eat: ({ human }: EatDeps): Eat => () => { + const bowl: Food[] = []; + human.fillBowl('tuna', bowl); + + while (bowl.length > 0) { + // have a nibble + bowl.pop(); + } + } + } }; const { compose } = composer(modules); +// compose as-is with no dependencies +const { util } = compose.asis('util'); +expectType(util); + // compose with no dependencies -expectType<{ foo: { doAThing: () => void }}>(compose('foo', {})); +const { food } = compose('food', {}); +expectType<{ serve: ServeFood }>(food); // compose with a dependency -const { foo } = compose('foo', {}); -expectType<{ bar: { doAThing: () => number }}>(compose('bar', { foo })); +const { human } = compose('human', { food }); +expectType<{ fillBowl: FillBowl }>(human); -// compose as-is with no dependencies -expectType<{ foo: { doAThing: () => () => void } }>(compose.asis('foo')); +// compose with no dependencies +const { toys } = compose('toys', { food }); +expectType<{ randomToy: RandomToy }>(toys); + +// compose with multiple dependencies across functions +const { cat } = compose('cat', { toys, util, human }); +expectType<{ play: Play, eat: Eat }>(cat); From b625aff74fa62e6dfd07159f6eb5afafadec7f88 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 15:22:16 +1000 Subject: [PATCH 05/33] chore: formatting --- .eslintrc.json | 28 ++++++++++++++++------------ main.test-d.ts | 36 ++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5dd08fe..f5142e3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -45,17 +45,21 @@ "off" ] }, - "overrides": [{ - "files": [ - "main.d.ts", - "main.test-d.ts" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"] - }], + "overrides": [ + { + "files": [ + "main.d.ts", + "main.test-d.ts" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ] + } + ], "plugins": [] } diff --git a/main.test-d.ts b/main.test-d.ts index 5ef7460..1d89e0b 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -5,11 +5,11 @@ import composer from './main'; type Toy = string type Food = string -type ServeFood = (food: Food, bowl: Food[]) => void -type FillBowl = (choice: Food, bowl: Food[]) => void -type RandomToy = () => Toy -type Play = () => void -type Eat = () => void +type ServeFood = (food: Food, bowl: Food[]) => void +type FillBowl = (choice: Food, bowl: Food[]) => void +type RandomToy = () => Toy +type Play = () => void +type Eat = () => void type HumanDeps = { food: { serve: ServeFood } @@ -17,45 +17,45 @@ type HumanDeps = { type PlayDeps = { toys: { randomToy: () => Toy } - util: { shuffle: (str: string) => string} + util: { shuffle: (str: string) => string } } type EatDeps = { human: { fillBowl: FillBowl } } -const modules = { - util : { +const modules = { + util: { shuffle: (str: string) => [...str].sort(() => Math.random() - 0.5).join('') }, - food: { + food: { serve: (): ServeFood => (food, bowl) => { bowl.push(food); } }, - - human: { + + human: { fillBowl: ({ food }: HumanDeps): FillBowl => (choice, bowl) => food.serve(choice, bowl) }, - - toys: { + + toys: { randomToy: (): RandomToy => () => 'yarn' }, - cat: { + cat: { play: ({ toys, util }: PlayDeps): Play => () => { const toy = toys.randomToy(); util.shuffle(toy); - } , + }, eat: ({ human }: EatDeps): Eat => () => { const bowl: Food[] = []; human.fillBowl('tuna', bowl); - + while (bowl.length > 0) { // have a nibble bowl.pop(); } - } + } } }; @@ -78,5 +78,5 @@ const { toys } = compose('toys', { food }); expectType<{ randomToy: RandomToy }>(toys); // compose with multiple dependencies across functions -const { cat } = compose('cat', { toys, util, human }); +const { cat } = compose('cat', { toys, util, human }); expectType<{ play: Play, eat: Eat }>(cat); From 83797902b81cfe0f5b478ced800d6a7da5061bda Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 15:26:00 +1000 Subject: [PATCH 06/33] refactor: human fills bowl --- main.test-d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.test-d.ts b/main.test-d.ts index 1d89e0b..0bad9ab 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -5,7 +5,7 @@ import composer from './main'; type Toy = string type Food = string -type ServeFood = (food: Food, bowl: Food[]) => void +type ServeFood = (food: Food) => Food[] type FillBowl = (choice: Food, bowl: Food[]) => void type RandomToy = () => Toy type Play = () => void @@ -30,11 +30,11 @@ const modules = { }, food: { - serve: (): ServeFood => (food, bowl) => { bowl.push(food); } + serve: (): ServeFood => (food) => [food, food, food] }, human: { - fillBowl: ({ food }: HumanDeps): FillBowl => (choice, bowl) => food.serve(choice, bowl) + fillBowl: ({ food }: HumanDeps): FillBowl => (choice, bowl) => bowl.concat(food.serve(choice)) }, toys: { From a7345c41b0e2aafa4bf626ad6c5be28cf88d52e1 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 3 Sep 2023 17:04:54 +1000 Subject: [PATCH 07/33] feat: export ComposedModule type --- main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index fffbe7a..87ab4b7 100644 --- a/main.d.ts +++ b/main.d.ts @@ -25,7 +25,7 @@ type Composable = (deps: Modules) => Composed type Module = Record type Modules = Record -type ComposedModule = { +export type ComposedModule = { [K in keyof T]: ReturnType } From e1b271e78493bc2a5523fb89ed3644cd5bd29b91 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 18:50:55 +1000 Subject: [PATCH 08/33] chore: formatting --- main.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.test-d.ts b/main.test-d.ts index 0bad9ab..613400c 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -30,7 +30,7 @@ const modules = { }, food: { - serve: (): ServeFood => (food) => [food, food, food] + serve: (): ServeFood => food => [food, food, food] }, human: { From a99e5b4dc3bc6c33f805d1e293b49128f199fe74 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 19:13:04 +1000 Subject: [PATCH 09/33] feat: omit config from deps if it is passed into compose --- main.d.ts | 12 ++++++------ main.test-d.ts | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/main.d.ts b/main.d.ts index 87ab4b7..ab0b2c0 100644 --- a/main.d.ts +++ b/main.d.ts @@ -1,4 +1,4 @@ -import type { UnionToIntersection } from 'type-fest'; +import type { UnionToIntersection, UnknownRecord } from 'type-fest'; interface CoreOptions { depth: number @@ -32,18 +32,18 @@ export type ComposedModule = { type ModuleParameters = Key extends PropertyKey ? Parameters[0] : never -type ModuleDependencies = UnionToIntersection>> +type ModuleDependencies = C extends UnknownRecord ? Omit>>, 'config'> : UnionToIntersection>> -type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> +type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> interface Asis { asis(path: Path, opts?: Partial): Record } -interface Composer { - compose: Compose & Asis +interface Composer { + compose: Compose & Asis } -declare function composer(config: T): Composer +declare function composer(target: T, config?: C): Composer export default composer; diff --git a/main.test-d.ts b/main.test-d.ts index 613400c..69a1a0f 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -15,6 +15,10 @@ type HumanDeps = { food: { serve: ServeFood } } +type RandomToyDeps = { + config: { availableToys: string[] } +} + type PlayDeps = { toys: { randomToy: () => Toy } util: { shuffle: (str: string) => string } @@ -38,7 +42,7 @@ const modules = { }, toys: { - randomToy: (): RandomToy => () => 'yarn' + randomToy: ({ config }: RandomToyDeps): RandomToy => () => config.availableToys[Math.floor(Math.random() * config.availableToys.length)] }, cat: { @@ -59,7 +63,7 @@ const modules = { } }; -const { compose } = composer(modules); +const { compose } = composer(modules, { config: { availableToys: ['yarn', 'bell', 'feather'] } }); // compose as-is with no dependencies const { util } = compose.asis('util'); @@ -73,10 +77,14 @@ expectType<{ serve: ServeFood }>(food); const { human } = compose('human', { food }); expectType<{ fillBowl: FillBowl }>(human); -// compose with no dependencies -const { toys } = compose('toys', { food }); +// compose with only config dependency +const { toys } = compose('toys', {}); expectType<{ randomToy: RandomToy }>(toys); // compose with multiple dependencies across functions const { cat } = compose('cat', { toys, util, human }); expectType<{ play: Play, eat: Eat }>(cat); + +// config is required as a dependency if it is not passed into the composer and an option +const { compose: composeWithoutConfig } = composer(modules); +composeWithoutConfig('toys', { config: { availableToys: ['yarn', 'bell', 'feather'] } }); From a380f51dd934a771cb5813527fcb43b9c9f591e4 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 19:22:59 +1000 Subject: [PATCH 10/33] feat: omit config from deps if it is passed into compose --- main.d.ts | 13 +++++++++---- main.test-d.ts | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/main.d.ts b/main.d.ts index ab0b2c0..7e60fec 100644 --- a/main.d.ts +++ b/main.d.ts @@ -19,6 +19,11 @@ interface ExtensionOptions { type Options = CoreOptions & ExtensionOptions +type ComposerOptionsConfig = UnknownRecord | UnknownRecord[] +interface ComposerOptions extends UnknownRecord { + config?: ComposerOptionsConfig +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any type Composed = (...args: any[]) => unknown type Composable = (deps: Modules) => Composed @@ -32,18 +37,18 @@ export type ComposedModule = { type ModuleParameters = Key extends PropertyKey ? Parameters[0] : never -type ModuleDependencies = C extends UnknownRecord ? Omit>>, 'config'> : UnionToIntersection>> +type ModuleDependencies = C['config'] extends ComposerOptionsConfig ? Omit>>, 'config'> : UnionToIntersection>> -type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> +type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> interface Asis { asis(path: Path, opts?: Partial): Record } -interface Composer { +interface Composer { compose: Compose & Asis } -declare function composer(target: T, config?: C): Composer +declare function composer(target: T, config?: C): Composer export default composer; diff --git a/main.test-d.ts b/main.test-d.ts index 69a1a0f..a886cdd 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -88,3 +88,7 @@ expectType<{ play: Play, eat: Eat }>(cat); // config is required as a dependency if it is not passed into the composer and an option const { compose: composeWithoutConfig } = composer(modules); composeWithoutConfig('toys', { config: { availableToys: ['yarn', 'bell', 'feather'] } }); + +// config is not required as a dependency when passing a list in +const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, { availableToys: ['yarn', 'bell', 'feather'] }] }); +composeWithListOfConfig('toys', {}); From ed5089adc659306a41d4ba50ae7ae0547ff7f820 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 19:30:09 +1000 Subject: [PATCH 11/33] refactor: dry config --- main.test-d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/main.test-d.ts b/main.test-d.ts index a886cdd..3e91989 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -63,7 +63,9 @@ const modules = { } }; -const { compose } = composer(modules, { config: { availableToys: ['yarn', 'bell', 'feather'] } }); +const config = { availableToys: ['yarn', 'bell', 'feather'] }; + +const { compose } = composer(modules, { config }); // compose as-is with no dependencies const { util } = compose.asis('util'); @@ -87,8 +89,8 @@ expectType<{ play: Play, eat: Eat }>(cat); // config is required as a dependency if it is not passed into the composer and an option const { compose: composeWithoutConfig } = composer(modules); -composeWithoutConfig('toys', { config: { availableToys: ['yarn', 'bell', 'feather'] } }); +composeWithoutConfig('toys', { config }); // config is not required as a dependency when passing a list in -const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, { availableToys: ['yarn', 'bell', 'feather'] }] }); +const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, config] }); composeWithListOfConfig('toys', {}); From ce8e5e001282182a39dcac108c4be562053a6a1e Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 19:35:45 +1000 Subject: [PATCH 12/33] chore: formatting --- main.d.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/main.d.ts b/main.d.ts index 7e60fec..79f021d 100644 --- a/main.d.ts +++ b/main.d.ts @@ -20,7 +20,7 @@ interface ExtensionOptions { type Options = CoreOptions & ExtensionOptions type ComposerOptionsConfig = UnknownRecord | UnknownRecord[] -interface ComposerOptions extends UnknownRecord { +interface ComposerOptions { config?: ComposerOptionsConfig } @@ -37,7 +37,10 @@ export type ComposedModule = { type ModuleParameters = Key extends PropertyKey ? Parameters[0] : never -type ModuleDependencies = C['config'] extends ComposerOptionsConfig ? Omit>>, 'config'> : UnionToIntersection>> +type ModuleDependencies = + C['config'] extends ComposerOptionsConfig + ? Omit>>, 'config'> + : UnionToIntersection>> type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> From 8187066e5d975f454335fb44ca1aa140ccd9699c Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Mon, 4 Sep 2023 19:59:13 +1000 Subject: [PATCH 13/33] feat: exclude self from deps --- main.d.ts | 2 +- main.test-d.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index 79f021d..8cba280 100644 --- a/main.d.ts +++ b/main.d.ts @@ -42,7 +42,7 @@ type ModuleDependencies = ? Omit>>, 'config'> : UnionToIntersection>> -type Compose = (path: Path, deps: ModuleDependencies, opts?: Partial) => Record> +type Compose = (path: Path, deps: Omit, Path>, opts?: Partial) => Record> interface Asis { asis(path: Path, opts?: Partial): Record diff --git a/main.test-d.ts b/main.test-d.ts index 3e91989..108f84e 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -10,6 +10,8 @@ type FillBowl = (choice: Food, bowl: Food[]) => void type RandomToy = () => Toy type Play = () => void type Eat = () => void +type BrushTeeth = () => void +type TidyUp = () => void type HumanDeps = { food: { serve: ServeFood } @@ -28,6 +30,10 @@ type EatDeps = { human: { fillBowl: FillBowl } } +type UsesSelfDeps = { + usesSelf: { brushTeeth: () => void } +} + const modules = { util: { shuffle: (str: string) => [...str].sort(() => Math.random() - 0.5).join('') @@ -60,6 +66,11 @@ const modules = { bowl.pop(); } } + }, + + usesSelf: { + brushTeeth: (): BrushTeeth => () => { }, + tidyUp: ({ usesSelf }: UsesSelfDeps): TidyUp => () => { usesSelf.brushTeeth(); } } }; @@ -87,6 +98,10 @@ expectType<{ randomToy: RandomToy }>(toys); const { cat } = compose('cat', { toys, util, human }); expectType<{ play: Play, eat: Eat }>(cat); +// self dependencies are not required to be provided +const { usesSelf } = compose('usesSelf', {}); +expectType<{ brushTeeth: BrushTeeth, tidyUp: TidyUp }>(usesSelf); + // config is required as a dependency if it is not passed into the composer and an option const { compose: composeWithoutConfig } = composer(modules); composeWithoutConfig('toys', { config }); From c7f44f2a95b8816147a42c602c2d50bd952b8eaa Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Tue, 5 Sep 2023 09:24:12 +1000 Subject: [PATCH 14/33] feat: use setup composable to compose the module when it exists --- main.d.ts | 11 +++++++++-- main.test-d.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/main.d.ts b/main.d.ts index 8cba280..937c85a 100644 --- a/main.d.ts +++ b/main.d.ts @@ -25,15 +25,22 @@ interface ComposerOptions { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -type Composed = (...args: any[]) => unknown +type Composed = (...args: any[]) => any type Composable = (deps: Modules) => Composed +type SetupComposable = { setup: (deps: Modules) => Composed } type Module = Record type Modules = Record -export type ComposedModule = { +type ComposedBySetup = ReturnType> +type ComposedIndividually = { [K in keyof T]: ReturnType } +export type ComposedModule = + T extends SetupComposable + ? ComposedBySetup + : ComposedIndividually + type ModuleParameters = Key extends PropertyKey ? Parameters[0] : never diff --git a/main.test-d.ts b/main.test-d.ts index 108f84e..ab9f2de 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -71,6 +71,12 @@ const modules = { usesSelf: { brushTeeth: (): BrushTeeth => () => { }, tidyUp: ({ usesSelf }: UsesSelfDeps): TidyUp => () => { usesSelf.brushTeeth(); } + }, + + setupModule: { + setup: () => () => ({ + green: () => { } + }) } }; @@ -109,3 +115,7 @@ composeWithoutConfig('toys', { config }); // config is not required as a dependency when passing a list in const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, config] }); composeWithListOfConfig('toys', {}); + +// module is composed using the setup function +const { setupModule } = compose('setupModule', {}); +expectType<{ green: () => void }>(setupModule); \ No newline at end of file From f5e6b4f8b5ddfef7265bff07eb012f68dab206d0 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Tue, 5 Sep 2023 09:28:31 +1000 Subject: [PATCH 15/33] chore: formatting --- main.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.test-d.ts b/main.test-d.ts index ab9f2de..5318ee4 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -118,4 +118,4 @@ composeWithListOfConfig('toys', {}); // module is composed using the setup function const { setupModule } = compose('setupModule', {}); -expectType<{ green: () => void }>(setupModule); \ No newline at end of file +expectType<{ green: () => void }>(setupModule); From 06412299ad7640e16fc8380ace0cf98b80907932 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Wed, 6 Sep 2023 16:14:33 +1000 Subject: [PATCH 16/33] feat: support modules within modules --- main.d.ts | 19 ++++++++++++++++--- main.test-d.ts | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/main.d.ts b/main.d.ts index 937c85a..58cf599 100644 --- a/main.d.ts +++ b/main.d.ts @@ -27,13 +27,22 @@ interface ComposerOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any type Composed = (...args: any[]) => any type Composable = (deps: Modules) => Composed +type ModuleFunction = Composable | Composed type SetupComposable = { setup: (deps: Modules) => Composed } -type Module = Record + +type Module = { + [K in keyof T]: T[K] extends ModuleFunction + ? ModuleFunction + : Module +} + type Modules = Record type ComposedBySetup = ReturnType> type ComposedIndividually = { - [K in keyof T]: ReturnType + [K in keyof T]: T[K] extends ModuleFunction + ? ReturnType + : ComposedModule } export type ComposedModule = @@ -42,7 +51,11 @@ export type ComposedModule = : ComposedIndividually type ModuleParameters = - Key extends PropertyKey ? Parameters[0] : never + Key extends PropertyKey + ? T[Key] extends ModuleFunction + ? Parameters[0] + : ModuleParameters + : never type ModuleDependencies = C['config'] extends ComposerOptionsConfig diff --git a/main.test-d.ts b/main.test-d.ts index 5318ee4..0992c50 100644 --- a/main.test-d.ts +++ b/main.test-d.ts @@ -77,6 +77,14 @@ const modules = { setup: () => () => ({ green: () => { } }) + }, + + flattenMe: { + module: { + mod1: { fun1: () => () => 1 }, + mod2: { fun2: () => () => 2 } + }, + mod3: { fun3: () => () => 2 } } }; From ab48c8ddf2a9a861d9fc480c44d34222233452f4 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Wed, 6 Sep 2023 18:14:21 +1000 Subject: [PATCH 17/33] feat: add support for deep --- .eslintrc.json | 3 +- main.d.ts | 12 +++++-- test-d/asis.test-d.ts | 15 +++++++++ test-d/compose.test-d.ts | 10 ++++++ test-d/deep.test-d.ts | 43 +++++++++++++++++++++++++ main.test-d.ts => test-d/main.test-d.ts | 2 +- 6 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 test-d/asis.test-d.ts create mode 100644 test-d/compose.test-d.ts create mode 100644 test-d/deep.test-d.ts rename main.test-d.ts => test-d/main.test-d.ts (99%) diff --git a/.eslintrc.json b/.eslintrc.json index f5142e3..3fa1674 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,8 +48,7 @@ "overrides": [ { "files": [ - "main.d.ts", - "main.test-d.ts" + "*.ts" ], "extends": [ "eslint:recommended", diff --git a/main.d.ts b/main.d.ts index 58cf599..d92a61d 100644 --- a/main.d.ts +++ b/main.d.ts @@ -62,14 +62,22 @@ type ModuleDependencies = ? Omit>>, 'config'> : UnionToIntersection>> -type Compose = (path: Path, deps: Omit, Path>, opts?: Partial) => Record> +type Compose = (path: Path, deps: Omit, Path | 'self'>, opts?: Partial) => Record> interface Asis { asis(path: Path, opts?: Partial): Record } +interface Deep { + deep: Compose +} + +interface Target { + target: T +} + interface Composer { - compose: Compose & Asis + compose: Target & Compose & Asis & Deep } declare function composer(target: T, config?: C): Composer diff --git a/test-d/asis.test-d.ts b/test-d/asis.test-d.ts new file mode 100644 index 0000000..fb0be3e --- /dev/null +++ b/test-d/asis.test-d.ts @@ -0,0 +1,15 @@ +import { expectType } from 'tsd/dist/index'; + +import composer from '../main'; + +// compose as-is with no dependencies +{ + const target = { + mod: { + fun: () => 1 + } + }; + const { compose } = composer(target); + const { mod } = compose.asis('mod'); + expectType<() => number>(mod.fun); +} diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts new file mode 100644 index 0000000..368f3c7 --- /dev/null +++ b/test-d/compose.test-d.ts @@ -0,0 +1,10 @@ +import { expectType } from 'tsd/dist/index'; + +import composer from '../main'; + +// accessing original target reference +{ + const target = { mod: {} }; + const { compose } = composer(target); + expectType(compose.target); +} diff --git a/test-d/deep.test-d.ts b/test-d/deep.test-d.ts new file mode 100644 index 0000000..f496261 --- /dev/null +++ b/test-d/deep.test-d.ts @@ -0,0 +1,43 @@ +import { expectType } from 'tsd/dist/index'; + +import composer from '../main'; + +// deep module +{ + const target = { + mod1: { + sub: { + fun: () => () => 'foobar' + } + }, + mod2: { + sub: { + fun: ({ mod2 }: { mod2: { sub: { fun: () => string } } }) => () => mod2.sub.fun() + } + } + }; + + const { compose } = composer(target); + const { mod1 } = compose.deep('mod1', {}); + const { mod2 } = compose.deep('mod2', { mod1 }); + + expectType<() => string>(mod1.sub.fun); + expectType<() => string>(mod2.sub.fun); +} + +// self at depth +{ + const target = { + mod: { + sub: { + fun1: ({ self }: { self: { sub: { fun2: () => string } } }) => () => self.sub.fun2(), + fun2: () => () => 'foobar' + } + } + }; + const { compose } = composer(target); + const { mod } = compose.deep('mod', {}); + + expectType<() => string>(mod.sub.fun1); + expectType<() => string>(mod.sub.fun2); +} diff --git a/main.test-d.ts b/test-d/main.test-d.ts similarity index 99% rename from main.test-d.ts rename to test-d/main.test-d.ts index 0992c50..e2cef6c 100644 --- a/main.test-d.ts +++ b/test-d/main.test-d.ts @@ -1,6 +1,6 @@ import { expectType } from 'tsd/dist/index'; -import composer from './main'; +import composer from '../main'; type Toy = string type Food = string From 426850bfd2500790f774f075d8b96593eccd5b3c Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Thu, 7 Sep 2023 17:30:37 +1000 Subject: [PATCH 18/33] feat: allow non-module properties --- main.d.ts | 7 ++++--- test-d/compose.test-d.ts | 9 ++++++++- test-d/composer.test-d.ts | 13 +++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 test-d/composer.test-d.ts diff --git a/main.d.ts b/main.d.ts index d92a61d..a360da4 100644 --- a/main.d.ts +++ b/main.d.ts @@ -1,4 +1,4 @@ -import type { UnionToIntersection, UnknownRecord } from 'type-fest'; +import type { ConditionalKeys, UnionToIntersection, UnknownRecord } from 'type-fest'; interface CoreOptions { depth: number @@ -36,7 +36,7 @@ type Module = { : Module } -type Modules = Record +type Modules = Record type ComposedBySetup = ReturnType> type ComposedIndividually = { @@ -62,7 +62,8 @@ type ModuleDependencies = ? Omit>>, 'config'> : UnionToIntersection>> -type Compose = (path: Path, deps: Omit, Path | 'self'>, opts?: Partial) => Record> +type ModulePath = T extends Module ? T[Path] : never +type Compose = >(path: Path, deps: Omit, C>, Path | 'self'>, opts?: Partial) => Record>> interface Asis { asis(path: Path, opts?: Partial): Record diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index 368f3c7..2e25243 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -1,4 +1,4 @@ -import { expectType } from 'tsd/dist/index'; +import { expectError, expectType } from 'tsd/dist/index'; import composer from '../main'; @@ -8,3 +8,10 @@ import composer from '../main'; const { compose } = composer(target); expectType(compose.target); } + +// target keys that are not plain objects are omitted from dependencies list +{ + const target = { mod: '' }; + const { compose } = composer(target); + expectError(compose('mod', {})); +} diff --git a/test-d/composer.test-d.ts b/test-d/composer.test-d.ts new file mode 100644 index 0000000..12df746 --- /dev/null +++ b/test-d/composer.test-d.ts @@ -0,0 +1,13 @@ +import composer from '../main'; + +// allow non-module properties +{ + const target = { + mod: {}, + a: '', + b: 1, + c: [] + }; + + composer(target); +} From b70e99368ff3c02198e98fada1419f5861629cbf Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Fri, 8 Sep 2023 07:11:39 +1000 Subject: [PATCH 19/33] refactor: self tests into their own file --- test-d/compose.test-d.ts | 12 +++++++++++ test-d/main.test-d.ts | 25 ----------------------- test-d/self.test-d.ts | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 test-d/self.test-d.ts diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index 2e25243..a588801 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -15,3 +15,15 @@ import composer from '../main'; const { compose } = composer(target); expectError(compose('mod', {})); } + +// result of module's setup function is returned from compose +{ + const target = { mod: { + setup: () => () => ({ + green: () => { } + }) + } }; + const { compose } = composer(target); + const { mod } = compose('mod', {}); + expectType<{ green: () => void }>(mod); +} diff --git a/test-d/main.test-d.ts b/test-d/main.test-d.ts index e2cef6c..a111014 100644 --- a/test-d/main.test-d.ts +++ b/test-d/main.test-d.ts @@ -10,8 +10,6 @@ type FillBowl = (choice: Food, bowl: Food[]) => void type RandomToy = () => Toy type Play = () => void type Eat = () => void -type BrushTeeth = () => void -type TidyUp = () => void type HumanDeps = { food: { serve: ServeFood } @@ -30,10 +28,6 @@ type EatDeps = { human: { fillBowl: FillBowl } } -type UsesSelfDeps = { - usesSelf: { brushTeeth: () => void } -} - const modules = { util: { shuffle: (str: string) => [...str].sort(() => Math.random() - 0.5).join('') @@ -68,17 +62,6 @@ const modules = { } }, - usesSelf: { - brushTeeth: (): BrushTeeth => () => { }, - tidyUp: ({ usesSelf }: UsesSelfDeps): TidyUp => () => { usesSelf.brushTeeth(); } - }, - - setupModule: { - setup: () => () => ({ - green: () => { } - }) - }, - flattenMe: { module: { mod1: { fun1: () => () => 1 }, @@ -112,10 +95,6 @@ expectType<{ randomToy: RandomToy }>(toys); const { cat } = compose('cat', { toys, util, human }); expectType<{ play: Play, eat: Eat }>(cat); -// self dependencies are not required to be provided -const { usesSelf } = compose('usesSelf', {}); -expectType<{ brushTeeth: BrushTeeth, tidyUp: TidyUp }>(usesSelf); - // config is required as a dependency if it is not passed into the composer and an option const { compose: composeWithoutConfig } = composer(modules); composeWithoutConfig('toys', { config }); @@ -123,7 +102,3 @@ composeWithoutConfig('toys', { config }); // config is not required as a dependency when passing a list in const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, config] }); composeWithListOfConfig('toys', {}); - -// module is composed using the setup function -const { setupModule } = compose('setupModule', {}); -expectType<{ green: () => void }>(setupModule); diff --git a/test-d/self.test-d.ts b/test-d/self.test-d.ts new file mode 100644 index 0000000..043e8c5 --- /dev/null +++ b/test-d/self.test-d.ts @@ -0,0 +1,44 @@ +import { expectType } from 'tsd/dist/index'; + +import composer from '../main'; + +// self dependencies are not required as deps (self reference by name) +{ + const target = { + mod: { + fun1: () => () => 1, + fun2: ({ mod }: { mod: { fun1: () => number } }) => () => mod.fun1() + } + }; + const { compose } = composer(target); + const { mod } = compose('mod', {}); + expectType<() => number>(mod.fun2); +} + +// self dependencies are not required as deps (literal self) +{ + const target = { + mod: { + fun1: () => () => 1, + fun2: ({ self }: { self: { fun1: () => number } }) => () => self.fun1() + } + }; + const { compose } = composer(target); + const { mod } = compose('mod', {}); + expectType<() => number>(mod.fun2); +} + +// self dependencies are not required as deps (literal self in deep module) +{ + const target = { + mod: { + fun1: () => () => 1, + sub: { + fun2: ({ self }) => () => self.fun1() + } + } + }; + const { compose } = composer(target); + const { mod } = compose('mod', {}); + expectType<() => number>(mod.sub.fun2); +} From 136914a0b29090d24cbbb6c6dee5fb6ee418d07f Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Fri, 8 Sep 2023 07:30:27 +1000 Subject: [PATCH 20/33] refactor: optional deps if the module has no deps --- main.d.ts | 12 ++++++++++-- test-d/compose.test-d.ts | 4 ++-- test-d/deep.test-d.ts | 4 ++-- test-d/main.test-d.ts | 6 +++--- test-d/self.test-d.ts | 8 ++++---- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/main.d.ts b/main.d.ts index a360da4..01b8bc5 100644 --- a/main.d.ts +++ b/main.d.ts @@ -1,4 +1,4 @@ -import type { ConditionalKeys, UnionToIntersection, UnknownRecord } from 'type-fest'; +import type { ConditionalKeys, EmptyObject, UnionToIntersection, UnknownRecord } from 'type-fest'; interface CoreOptions { depth: number @@ -62,8 +62,16 @@ type ModuleDependencies = ? Omit>>, 'config'> : UnionToIntersection>> +type Deps = Omit, C>, Path | 'self'> +type ComposeResult = Record>> + +type ModuleKeys = ConditionalKeys type ModulePath = T extends Module ? T[Path] : never -type Compose = >(path: Path, deps: Omit, C>, Path | 'self'>, opts?: Partial) => Record>> + +type Compose = , PathDeps = Deps>( + path: Path, + ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] +) => ComposeResult interface Asis { asis(path: Path, opts?: Partial): Record diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index a588801..a38bc46 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -13,7 +13,7 @@ import composer from '../main'; { const target = { mod: '' }; const { compose } = composer(target); - expectError(compose('mod', {})); + expectError(compose('mod')); } // result of module's setup function is returned from compose @@ -24,6 +24,6 @@ import composer from '../main'; }) } }; const { compose } = composer(target); - const { mod } = compose('mod', {}); + const { mod } = compose('mod'); expectType<{ green: () => void }>(mod); } diff --git a/test-d/deep.test-d.ts b/test-d/deep.test-d.ts index f496261..a5b5045 100644 --- a/test-d/deep.test-d.ts +++ b/test-d/deep.test-d.ts @@ -18,7 +18,7 @@ import composer from '../main'; }; const { compose } = composer(target); - const { mod1 } = compose.deep('mod1', {}); + const { mod1 } = compose.deep('mod1'); const { mod2 } = compose.deep('mod2', { mod1 }); expectType<() => string>(mod1.sub.fun); @@ -36,7 +36,7 @@ import composer from '../main'; } }; const { compose } = composer(target); - const { mod } = compose.deep('mod', {}); + const { mod } = compose.deep('mod'); expectType<() => string>(mod.sub.fun1); expectType<() => string>(mod.sub.fun2); diff --git a/test-d/main.test-d.ts b/test-d/main.test-d.ts index a111014..ceb368d 100644 --- a/test-d/main.test-d.ts +++ b/test-d/main.test-d.ts @@ -80,7 +80,7 @@ const { util } = compose.asis('util'); expectType(util); // compose with no dependencies -const { food } = compose('food', {}); +const { food } = compose('food'); expectType<{ serve: ServeFood }>(food); // compose with a dependency @@ -88,7 +88,7 @@ const { human } = compose('human', { food }); expectType<{ fillBowl: FillBowl }>(human); // compose with only config dependency -const { toys } = compose('toys', {}); +const { toys } = compose('toys'); expectType<{ randomToy: RandomToy }>(toys); // compose with multiple dependencies across functions @@ -101,4 +101,4 @@ composeWithoutConfig('toys', { config }); // config is not required as a dependency when passing a list in const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, config] }); -composeWithListOfConfig('toys', {}); +composeWithListOfConfig('toys'); diff --git a/test-d/self.test-d.ts b/test-d/self.test-d.ts index 043e8c5..4804cbc 100644 --- a/test-d/self.test-d.ts +++ b/test-d/self.test-d.ts @@ -11,7 +11,7 @@ import composer from '../main'; } }; const { compose } = composer(target); - const { mod } = compose('mod', {}); + const { mod } = compose('mod'); expectType<() => number>(mod.fun2); } @@ -24,7 +24,7 @@ import composer from '../main'; } }; const { compose } = composer(target); - const { mod } = compose('mod', {}); + const { mod } = compose('mod'); expectType<() => number>(mod.fun2); } @@ -34,11 +34,11 @@ import composer from '../main'; mod: { fun1: () => () => 1, sub: { - fun2: ({ self }) => () => self.fun1() + fun2: ({ self }: { self: { fun1: () => number } }) => () => self.fun1() } } }; const { compose } = composer(target); - const { mod } = compose('mod', {}); + const { mod } = compose('mod'); expectType<() => number>(mod.sub.fun2); } From bd265b8c9faf817213964f4228cb91155bcd71af Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Fri, 8 Sep 2023 08:26:48 +1000 Subject: [PATCH 21/33] refactor: limit depth of compose to 1 --- main.d.ts | 27 +++++++++++++++++++-------- test-d/compose.test-d.ts | 35 ++++++++++++++++++++++++++++++----- test-d/self.test-d.ts | 2 +- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/main.d.ts b/main.d.ts index 01b8bc5..280eb42 100644 --- a/main.d.ts +++ b/main.d.ts @@ -38,17 +38,23 @@ type Module = { type Modules = Record +type AllowedMaxDepth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 +type IncrementDepth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10][N] + type ComposedBySetup = ReturnType> -type ComposedIndividually = { - [K in keyof T]: T[K] extends ModuleFunction +type ComposedIndividually = { + [K in keyof T]: + N extends MaxDepth + ? T[K] + : T[K] extends ModuleFunction ? ReturnType - : ComposedModule + : ComposedModule> } -export type ComposedModule = +export type ComposedModule = T extends SetupComposable ? ComposedBySetup - : ComposedIndividually + : ComposedIndividually type ModuleParameters = Key extends PropertyKey @@ -63,7 +69,7 @@ type ModuleDependencies = : UnionToIntersection>> type Deps = Omit, C>, Path | 'self'> -type ComposeResult = Record>> +type ComposeResult = Record, MaxDepth>> type ModuleKeys = ConditionalKeys type ModulePath = T extends Module ? T[Path] : never @@ -71,14 +77,19 @@ type ModulePath = T extends Module ? T[Path] : never type Compose = , PathDeps = Deps>( path: Path, ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] -) => ComposeResult +) => ComposeResult + +type DeepCompose = , PathDeps = Deps>( + path: Path, + ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] +) => ComposeResult interface Asis { asis(path: Path, opts?: Partial): Record } interface Deep { - deep: Compose + deep: DeepCompose } interface Target { diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index a38bc46..7b68c19 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -18,12 +18,37 @@ import composer from '../main'; // result of module's setup function is returned from compose { - const target = { mod: { - setup: () => () => ({ - green: () => { } - }) - } }; + const target = { + mod: { + setup: () => () => ({ + green: () => { } + }) + } + }; const { compose } = composer(target); const { mod } = compose('mod'); expectType<{ green: () => void }>(mod); } + +// compose does not go deep +{ + const target = { + mod1: { + sub: { + fun: () => () => 'foobar' + } + }, + mod2: { + sub: { + fun: ({ mod2 }: { mod2: { sub: { fun: () => string } } }) => () => mod2.sub.fun() + } + } + }; + + const { compose } = composer(target); + const { mod1 } = compose('mod1'); + const { mod2 } = compose('mod2', { mod1 }); + + expectType<() => () => string>(mod1.sub.fun); + expectType<({ mod2 }: { mod2: { sub: { fun: () => string } } }) => () => string>(mod2.sub.fun); +} diff --git a/test-d/self.test-d.ts b/test-d/self.test-d.ts index 4804cbc..56cf31d 100644 --- a/test-d/self.test-d.ts +++ b/test-d/self.test-d.ts @@ -39,6 +39,6 @@ import composer from '../main'; } }; const { compose } = composer(target); - const { mod } = compose('mod'); + const { mod } = compose.deep('mod'); expectType<() => number>(mod.sub.fun2); } From f89fbf4da0cc848c74c45e46b041f32dc5abff0d Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Fri, 8 Sep 2023 09:00:57 +1000 Subject: [PATCH 22/33] refactor: remove exported type --- main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index 280eb42..71960fb 100644 --- a/main.d.ts +++ b/main.d.ts @@ -51,7 +51,7 @@ type ComposedIndividually> } -export type ComposedModule = +type ComposedModule = T extends SetupComposable ? ComposedBySetup : ComposedIndividually From 82e171ad8e1b8d7e9c4c42fd0b21950c7337116e Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 10 Sep 2023 19:34:43 +1000 Subject: [PATCH 23/33] refactor: simplify type --- main.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.d.ts b/main.d.ts index 71960fb..1270bf2 100644 --- a/main.d.ts +++ b/main.d.ts @@ -1,4 +1,4 @@ -import type { ConditionalKeys, EmptyObject, UnionToIntersection, UnknownRecord } from 'type-fest'; +import type { ConditionalKeys, EmptyObject, Simplify, UnionToIntersection, UnknownRecord } from 'type-fest'; interface CoreOptions { depth: number @@ -54,7 +54,7 @@ type ComposedIndividually = T extends SetupComposable ? ComposedBySetup - : ComposedIndividually + : Simplify> type ModuleParameters = Key extends PropertyKey From 01405cd4a36db7b34df321de8523df76eee56578 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Fri, 13 Oct 2023 12:44:00 +1100 Subject: [PATCH 24/33] fix: correctly detect setup functions --- main.d.ts | 4 +++- test-d/compose.test-d.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index 1270bf2..70fe924 100644 --- a/main.d.ts +++ b/main.d.ts @@ -26,9 +26,11 @@ interface ComposerOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any type Composed = (...args: any[]) => any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type SetupComposable = { setup: (deps: any) => Composed } type Composable = (deps: Modules) => Composed type ModuleFunction = Composable | Composed -type SetupComposable = { setup: (deps: Modules) => Composed } + type Module = { [K in keyof T]: T[K] extends ModuleFunction diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index 7b68c19..62bbb85 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -30,6 +30,20 @@ import composer from '../main'; expectType<{ green: () => void }>(mod); } +// result of module's setup function is returned from compose with dependencies +{ + const target = { + mod: { + setup: ({ config }: { config: object}) => () => ({ + green: () => config + }) + } + }; + const { compose } = composer(target, { config: { shade: 'dark'}}); + const { mod } = compose('mod'); + expectType<{ green: () => object }>(mod); +} + // compose does not go deep { const target = { From 77b0fce686a9c17464eae2d5515ea15ce3e5fdac Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sat, 21 Oct 2023 16:24:40 +1100 Subject: [PATCH 25/33] feat: expose a type to type modules --- main.d.ts | 26 ++++++++++++++++---------- test-d/self.test-d.ts | 27 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/main.d.ts b/main.d.ts index 70fe924..e011e57 100644 --- a/main.d.ts +++ b/main.d.ts @@ -31,7 +31,6 @@ type SetupComposable = { setup: (deps: any) => Composed } type Composable = (deps: Modules) => Composed type ModuleFunction = Composable | Composed - type Module = { [K in keyof T]: T[K] extends ModuleFunction ? ModuleFunction @@ -43,20 +42,27 @@ type Modules = Record type AllowedMaxDepth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 type IncrementDepth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10][N] -type ComposedBySetup = ReturnType> -type ComposedIndividually = { +type ComposeEach = { [K in keyof T]: N extends MaxDepth ? T[K] : T[K] extends ModuleFunction ? ReturnType - : ComposedModule> + : ComposeEach> } -type ComposedModule = +type ComposeType = 'Single' | 'Flat' +export type ComposedModule = + CT extends 'Single' + ? Simplify> + : Simplify> + +type SetupModule = ReturnType> + +type ComposedOrSetupModule = T extends SetupComposable - ? ComposedBySetup - : Simplify> + ? SetupModule + : ComposedModule type ModuleParameters = Key extends PropertyKey @@ -71,7 +77,7 @@ type ModuleDependencies = : UnionToIntersection>> type Deps = Omit, C>, Path | 'self'> -type ComposeResult = Record, MaxDepth>> +type ComposeResult = Record, CT>> type ModuleKeys = ConditionalKeys type ModulePath = T extends Module ? T[Path] : never @@ -79,12 +85,12 @@ type ModulePath = T extends Module ? T[Path] : never type Compose = , PathDeps = Deps>( path: Path, ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] -) => ComposeResult +) => ComposeResult type DeepCompose = , PathDeps = Deps>( path: Path, ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] -) => ComposeResult +) => ComposeResult interface Asis { asis(path: Path, opts?: Partial): Record diff --git a/test-d/self.test-d.ts b/test-d/self.test-d.ts index 56cf31d..56eff72 100644 --- a/test-d/self.test-d.ts +++ b/test-d/self.test-d.ts @@ -1,6 +1,6 @@ import { expectType } from 'tsd/dist/index'; -import composer from '../main'; +import composer, { ComposedModule } from '../main'; // self dependencies are not required as deps (self reference by name) { @@ -42,3 +42,28 @@ import composer from '../main'; const { mod } = compose.deep('mod'); expectType<() => number>(mod.sub.fun2); } + +// A self dependency can be typed for use in setup +{ + const createModule = () => { + const mod = { + foo: () => () => 'hello' + }; + + type Self = ComposedModule + + return { + ...mod, + setup: ({ self }: { self: Self }) => () => { + return { + process: () => self.foo() + }; + } + }; + }; + + const target = { mod: createModule() }; + const { compose } = composer(target); + const { mod } = compose('mod'); + expectType(mod.process()); +} From 4276c08590b9870e0f4718f519c03d2fd5311574 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sat, 21 Oct 2023 18:59:17 +1100 Subject: [PATCH 26/33] simplify and expose setup module --- main.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/main.d.ts b/main.d.ts index e011e57..94135cb 100644 --- a/main.d.ts +++ b/main.d.ts @@ -52,17 +52,19 @@ type ComposeEach = +export type ComposedModule = Simplify< CT extends 'Single' - ? Simplify> - : Simplify> + ? ComposeEach + : ComposeEach +> type SetupModule = ReturnType> -type ComposedOrSetupModule = +type ComposedOrSetupModule = Simplify< T extends SetupComposable ? SetupModule : ComposedModule +> type ModuleParameters = Key extends PropertyKey From a81ce6df2d48c781e07580677c68a2323add7065 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sat, 21 Oct 2023 19:02:14 +1100 Subject: [PATCH 27/33] export ComposedOrSetupModule --- main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index 94135cb..9897e21 100644 --- a/main.d.ts +++ b/main.d.ts @@ -60,7 +60,7 @@ export type ComposedModule type SetupModule = ReturnType> -type ComposedOrSetupModule = Simplify< +export type ComposedOrSetupModule = Simplify< T extends SetupComposable ? SetupModule : ComposedModule From f53c92a42d431e0740c5e629a1280cd8c28173e4 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Sun, 22 Oct 2023 00:12:15 +1100 Subject: [PATCH 28/33] fix: use object instead of unknown record for config --- main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index 9897e21..4215365 100644 --- a/main.d.ts +++ b/main.d.ts @@ -19,7 +19,7 @@ interface ExtensionOptions { type Options = CoreOptions & ExtensionOptions -type ComposerOptionsConfig = UnknownRecord | UnknownRecord[] +type ComposerOptionsConfig = object | object[] interface ComposerOptions { config?: ComposerOptionsConfig } From 73cb9b5980b2948c518d0437d9ced16883b4ba3a Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Tue, 24 Oct 2023 11:30:51 +1100 Subject: [PATCH 29/33] fix: enforce deps are passed in --- main.d.ts | 8 ++++---- test-d/compose.test-d.ts | 17 +++++++++++++++++ test-d/deep.test-d.ts | 21 ++++++++++++++++++++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/main.d.ts b/main.d.ts index 4215365..e50424c 100644 --- a/main.d.ts +++ b/main.d.ts @@ -84,14 +84,14 @@ type ComposeResult = ConditionalKeys type ModulePath = T extends Module ? T[Path] : never -type Compose = , PathDeps = Deps>( +type Compose = >( path: Path, - ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] + ...args: Deps extends EmptyObject ? [Deps?, Partial?] : [Deps, Partial?] ) => ComposeResult -type DeepCompose = , PathDeps = Deps>( +type DeepCompose = >( path: Path, - ...args: PathDeps extends EmptyObject ? [PathDeps?, Partial?] : [PathDeps, Partial?] + ...args: Deps extends EmptyObject ? [Deps?, Partial?] : [Deps, Partial?] ) => ComposeResult interface Asis { diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index 62bbb85..a33f6ce 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -66,3 +66,20 @@ import composer from '../main'; expectType<() => () => string>(mod1.sub.fun); expectType<({ mod2 }: { mod2: { sub: { fun: () => string } } }) => () => string>(mod2.sub.fun); } + +// must specify all dependencies when composing a module +{ + const target = { + mod2: { + fun: ({ mod1, mod3 }: { mod1: { fun: () => string }, mod3: { fun: () => string } }) => () => mod1.fun() + mod3.fun() + } + }; + + const { compose } = composer(target); + const mod1 = { fun: () => 'foobar' }; + const mod3 = { fun: () => 'foobar' }; + expectError(compose('mod2', { })); + expectError(compose('mod2', { mod3 })); + const { mod2 } = compose('mod2', { mod1, mod3 }); + expectType<() => string>(mod2.fun); +} diff --git a/test-d/deep.test-d.ts b/test-d/deep.test-d.ts index a5b5045..bdd6ccb 100644 --- a/test-d/deep.test-d.ts +++ b/test-d/deep.test-d.ts @@ -1,4 +1,4 @@ -import { expectType } from 'tsd/dist/index'; +import { expectType, expectError } from 'tsd/dist/index'; import composer from '../main'; @@ -41,3 +41,22 @@ import composer from '../main'; expectType<() => string>(mod.sub.fun1); expectType<() => string>(mod.sub.fun2); } + +// must specify all dependencies when composing a deep module +{ + const target = { + mod2: { + sub: { + fun: ({ mod1, mod3 }: { mod1: { fun: () => string }, mod3: { fun: () => string } }) => () => mod1.fun() + mod3.fun() + } + } + }; + + const { compose } = composer(target); + const mod1 = { fun: () => 'foobar' }; + const mod3 = { fun: () => 'foobar' }; + expectError(compose.deep('mod2', { })); + expectError(compose.deep('mod2', { mod3 })); + const { mod2 } = compose.deep('mod2', { mod1, mod3 }); + expectType<() => string>(mod2.sub.fun); +} From 73425e8bedb693b8a1e38696c841fcd2fedb5220 Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Tue, 24 Oct 2023 11:59:02 +1100 Subject: [PATCH 30/33] chore: move tests to individual files --- test-d/compose.test-d.ts | 72 +++++++++++++++++++++++++++ test-d/deep.test-d.ts | 13 +++++ test-d/main.test-d.ts | 104 --------------------------------------- 3 files changed, 85 insertions(+), 104 deletions(-) delete mode 100644 test-d/main.test-d.ts diff --git a/test-d/compose.test-d.ts b/test-d/compose.test-d.ts index a33f6ce..6570f70 100644 --- a/test-d/compose.test-d.ts +++ b/test-d/compose.test-d.ts @@ -83,3 +83,75 @@ import composer from '../main'; const { mod2 } = compose('mod2', { mod1, mod3 }); expectType<() => string>(mod2.fun); } + +// composing a module with no dependencies +{ + const target = { + mod2: { + fun: () => () => 'hello' + } + }; + + const { compose } = composer(target); + const { mod2 } = compose('mod2'); + expectType<() => string>(mod2.fun); +} + +// manually provide a config dependency +{ + type Config = { name: string, surname: string } + const target = { + mod1: { + fun: ({ config }: { config: Config }) => () => `hello ${config.name}` + }, + mod2: { + fun: ({ mod1, config }: { mod1: { fun: () => string }, config: Config }) => () => `${mod1.fun()} ${config.surname}` + } + }; + + const config: Config = { name: 'harry', surname: 'potter' }; + const { compose } = composer(target); + expectError(compose('mod1')); + expectError(compose('mod2', { mod1 })); + const { mod1 } = compose('mod1', { config }); + const { mod2 } = compose('mod2', { mod1, config }); + expectType<() => string>(mod2.fun); +} + +// config does not need to be specified as a dependency if passed into the composer +{ + type Config = { name: string, surname: string } + const target = { + mod1: { + fun: ({ config }: { config: Config }) => () => `hello ${config.name}` + }, + mod2: { + fun: ({ mod1, config }: { mod1: { fun: () => string }, config: Config }) => () => `${mod1.fun()} ${config.surname}` + } + }; + + const config: Config = { name: 'harry', surname: 'potter' }; + const { compose } = composer(target, { config }); + const { mod1 } = compose('mod1'); + const { mod2 } = compose('mod2', { mod1 }); + expectType<() => string>(mod2.fun); +} + +// config can be specified as a list of object to merge +{ + type Config = { name: string, surname: string } + const target = { + mod1: { + fun: ({ config }: { config: Config }) => () => `hello ${config.name}` + }, + mod2: { + fun: ({ mod1, config }: { mod1: { fun: () => string }, config: Config }) => () => `${mod1.fun()} ${config.surname}` + } + }; + + const config: Config = { name: 'harry', surname: 'potter' }; + const { compose } = composer(target, { config: [{}, config] }); + const { mod1 } = compose('mod1'); + const { mod2 } = compose('mod2', { mod1 }); + expectType<() => string>(mod2.fun); +} diff --git a/test-d/deep.test-d.ts b/test-d/deep.test-d.ts index bdd6ccb..904c4ae 100644 --- a/test-d/deep.test-d.ts +++ b/test-d/deep.test-d.ts @@ -60,3 +60,16 @@ import composer from '../main'; const { mod2 } = compose.deep('mod2', { mod1, mod3 }); expectType<() => string>(mod2.sub.fun); } + +// composing a deep module with no dependencies +{ + const target = { + mod2: { + sub: { fun: () => () => 'hello' } + } + }; + + const { compose } = composer(target); + const { mod2 } = compose.deep('mod2'); + expectType<() => string>(mod2.sub.fun); +} diff --git a/test-d/main.test-d.ts b/test-d/main.test-d.ts deleted file mode 100644 index ceb368d..0000000 --- a/test-d/main.test-d.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { expectType } from 'tsd/dist/index'; - -import composer from '../main'; - -type Toy = string -type Food = string - -type ServeFood = (food: Food) => Food[] -type FillBowl = (choice: Food, bowl: Food[]) => void -type RandomToy = () => Toy -type Play = () => void -type Eat = () => void - -type HumanDeps = { - food: { serve: ServeFood } -} - -type RandomToyDeps = { - config: { availableToys: string[] } -} - -type PlayDeps = { - toys: { randomToy: () => Toy } - util: { shuffle: (str: string) => string } -} - -type EatDeps = { - human: { fillBowl: FillBowl } -} - -const modules = { - util: { - shuffle: (str: string) => [...str].sort(() => Math.random() - 0.5).join('') - }, - - food: { - serve: (): ServeFood => food => [food, food, food] - }, - - human: { - fillBowl: ({ food }: HumanDeps): FillBowl => (choice, bowl) => bowl.concat(food.serve(choice)) - }, - - toys: { - randomToy: ({ config }: RandomToyDeps): RandomToy => () => config.availableToys[Math.floor(Math.random() * config.availableToys.length)] - }, - - cat: { - play: ({ toys, util }: PlayDeps): Play => () => { - const toy = toys.randomToy(); - util.shuffle(toy); - }, - - eat: ({ human }: EatDeps): Eat => () => { - const bowl: Food[] = []; - human.fillBowl('tuna', bowl); - - while (bowl.length > 0) { - // have a nibble - bowl.pop(); - } - } - }, - - flattenMe: { - module: { - mod1: { fun1: () => () => 1 }, - mod2: { fun2: () => () => 2 } - }, - mod3: { fun3: () => () => 2 } - } -}; - -const config = { availableToys: ['yarn', 'bell', 'feather'] }; - -const { compose } = composer(modules, { config }); - -// compose as-is with no dependencies -const { util } = compose.asis('util'); -expectType(util); - -// compose with no dependencies -const { food } = compose('food'); -expectType<{ serve: ServeFood }>(food); - -// compose with a dependency -const { human } = compose('human', { food }); -expectType<{ fillBowl: FillBowl }>(human); - -// compose with only config dependency -const { toys } = compose('toys'); -expectType<{ randomToy: RandomToy }>(toys); - -// compose with multiple dependencies across functions -const { cat } = compose('cat', { toys, util, human }); -expectType<{ play: Play, eat: Eat }>(cat); - -// config is required as a dependency if it is not passed into the composer and an option -const { compose: composeWithoutConfig } = composer(modules); -composeWithoutConfig('toys', { config }); - -// config is not required as a dependency when passing a list in -const { compose: composeWithListOfConfig } = composer(modules, { config: [{}, config] }); -composeWithListOfConfig('toys'); From ba51885cfd2137e6f9b651638c6bd1f506cad71e Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Wed, 25 Oct 2023 16:15:57 +1100 Subject: [PATCH 31/33] feat: utility types for Inject --- main.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.d.ts b/main.d.ts index e50424c..b0c583c 100644 --- a/main.d.ts +++ b/main.d.ts @@ -39,6 +39,9 @@ type Module = { type Modules = Record +type ValuesOf = T[keyof T] +type ModuleValuesOf = Extract, Module> + type AllowedMaxDepth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 type IncrementDepth = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10][N] @@ -51,6 +54,11 @@ type ComposeEach> } +export type SelfOf = ComposeEach, 1, 0> +export type SelfNameOf = keyof T +export type SpecifiedDepsOf = Exclude> | never +export type InjectOf> = Pick>, | K> & Record<'self' | SelfNameOf, SelfOf> + type ComposeType = 'Single' | 'Flat' export type ComposedModule = Simplify< CT extends 'Single' From 87c6e7b938f0de40f13644ca81fcb66bf310200b Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Wed, 25 Oct 2023 16:23:43 +1100 Subject: [PATCH 32/33] fix: omit setup from self --- main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index b0c583c..fd8642e 100644 --- a/main.d.ts +++ b/main.d.ts @@ -54,7 +54,7 @@ type ComposeEach> } -export type SelfOf = ComposeEach, 1, 0> +export type SelfOf = Omit, 1, 0>, 'setup'> export type SelfNameOf = keyof T export type SpecifiedDepsOf = Exclude> | never export type InjectOf> = Pick>, | K> & Record<'self' | SelfNameOf, SelfOf> From 82ceab9c3cc5b648874b5214f317f670194b604e Mon Sep 17 00:00:00 2001 From: Adam Woods Date: Wed, 25 Oct 2023 16:43:06 +1100 Subject: [PATCH 33/33] feat: support config in deps without specifying it --- main.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.d.ts b/main.d.ts index fd8642e..565d635 100644 --- a/main.d.ts +++ b/main.d.ts @@ -57,7 +57,10 @@ type ComposeEach = Omit, 1, 0>, 'setup'> export type SelfNameOf = keyof T export type SpecifiedDepsOf = Exclude> | never -export type InjectOf> = Pick>, | K> & Record<'self' | SelfNameOf, SelfOf> +export type InjectOf> = + Composed extends { config: unknown } + ? Pick>, | K> & Record<'self' | SelfNameOf, SelfOf> & Record<'config', Composed['config']> + : Pick>, | K> & Record<'self' | SelfNameOf, SelfOf> type ComposeType = 'Single' | 'Flat' export type ComposedModule = Simplify<