From feccce6edfda9edb56e9b57301da8fa95761db5f Mon Sep 17 00:00:00 2001 From: Gordon Brander Date: Sat, 22 Jun 2024 10:53:17 -0700 Subject: [PATCH 1/4] Rough out propagators lib --- sketches/2024-06-18-propagator-ui/.gitignore | 24 + sketches/2024-06-18-propagator-ui/index.html | 13 + .../package-lock.json | 814 ++++++++++++++++++ .../2024-06-18-propagator-ui/package.json | 15 + .../2024-06-18-propagator-ui/public/vite.svg | 1 + sketches/2024-06-18-propagator-ui/src/main.ts | 25 + .../src/propagators.ts | 210 +++++ .../2024-06-18-propagator-ui/tsconfig.json | 18 + 8 files changed, 1120 insertions(+) create mode 100644 sketches/2024-06-18-propagator-ui/.gitignore create mode 100644 sketches/2024-06-18-propagator-ui/index.html create mode 100644 sketches/2024-06-18-propagator-ui/package-lock.json create mode 100644 sketches/2024-06-18-propagator-ui/package.json create mode 100644 sketches/2024-06-18-propagator-ui/public/vite.svg create mode 100644 sketches/2024-06-18-propagator-ui/src/main.ts create mode 100644 sketches/2024-06-18-propagator-ui/src/propagators.ts create mode 100644 sketches/2024-06-18-propagator-ui/tsconfig.json diff --git a/sketches/2024-06-18-propagator-ui/.gitignore b/sketches/2024-06-18-propagator-ui/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/sketches/2024-06-18-propagator-ui/index.html b/sketches/2024-06-18-propagator-ui/index.html new file mode 100644 index 000000000..44a933506 --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + TS + + +
+ + + diff --git a/sketches/2024-06-18-propagator-ui/package-lock.json b/sketches/2024-06-18-propagator-ui/package-lock.json new file mode 100644 index 000000000..1e6ecbb7b --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/package-lock.json @@ -0,0 +1,814 @@ +{ + "name": "propagators", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "propagators", + "version": "0.0.0", + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.3.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/sketches/2024-06-18-propagator-ui/package.json b/sketches/2024-06-18-propagator-ui/package.json new file mode 100644 index 000000000..1449b75d9 --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/package.json @@ -0,0 +1,15 @@ +{ + "name": "propagators", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.3.1" + } +} diff --git a/sketches/2024-06-18-propagator-ui/public/vite.svg b/sketches/2024-06-18-propagator-ui/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sketches/2024-06-18-propagator-ui/src/main.ts b/sketches/2024-06-18-propagator-ui/src/main.ts new file mode 100644 index 000000000..b1b0de0eb --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/src/main.ts @@ -0,0 +1,25 @@ +import {Cell, cell, constant, sink, mul, sub, div} from './propagators.js'; + +function fahrenheitToCelsius(f: Cell, c: Cell) { + const thirtyTwo = constant(32); + const five = constant(5); + const nine = constant(9); + const fMin32 = cell(0); + const cMult9 = cell(0); + + sub(f, thirtyTwo, fMin32); + mul(fMin32, five, cMult9); + div(cMult9, nine, c); +} + +const input = cell(0); +const output = cell(0); + +fahrenheitToCelsius( + input, + output +); + +sink(output, value => { + console.log(value); +}); \ No newline at end of file diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts new file mode 100644 index 000000000..fa7ca3a3e --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts @@ -0,0 +1,210 @@ +export type Task = { + poll(): void; +}; + +class Scheduler { + #transaction = new Set(); + #isRunning = false; + + enqueue(propagators: Iterable) { + for (const propagator of propagators) { + this.#transaction.add(propagator); + } + this.#wake(); + } + + #wake() { + if (!this.#isRunning) { + this.#isRunning = true; + const current = this.#transaction; + this.#transaction = new Set(); + for (const task of current) { + try { + task.poll(); + } catch (error) { + console.error(error); + } + } + this.#isRunning = false + } + } +} + +const scheduler = new Scheduler(); + +export type Mergeable = { + merge(next: T): T +}; + +export const isMergeable = ( + value: any +): value is Mergeable => { + return value != null && typeof value.merge === "function"; +} + +/** Merge a value if type implements merge, or else use last-write-wins */ +export const merge = ( + prev: T, + next: T +): T => { + if (isMergeable(prev)) { + return prev.merge(next); + } + return next; +} + +export type Cancel = () => void; + +export type Cancellable = { + cancel(): void; +} + +export type AnyCell = { + content: T; + subscribe(propagator: Task): Cancel; +} + +export const isCell = ( + value: any +): value is AnyCell => { + return ( + value !== null && + Object.hasOwn(value, "content") && + typeof value.subscribe === "function" + ); +} + +export const subscribeAll = ( + cells: Array>, + propagator: Task +): Cancel => { + const cancels = cells.map(cell => cell.subscribe(propagator)); + return () => { + for (const cancel of cancels) { + cancel(); + } + } +} + +export class Cell implements AnyCell { + #neighbors: Set = new Set(); + #content: T; + + constructor(initial: T) { + this.#content = initial + } + + get content() { + return this.#content; + } + + send(next: T) { + const merged = merge(this.#content, next); + if (this.#content !== merged) { + this.#content = merged; + scheduler.enqueue(this.#neighbors); + } + } + + subscribe(propagator: Task) { + this.#neighbors.add(propagator); + scheduler.enqueue([propagator]); + return () => { + this.#neighbors.delete(propagator); + } + } +} + +export const cell = (initial: T) => new Cell(initial); + +/** A read-only view over a cell */ +export class CellView implements AnyCell { + #cell: Cell; + + constructor(cell: Cell) { + this.#cell = cell; + } + + get content() { + return this.#cell.content; + } + + subscribe(propagator: Task) { + return this.#cell.subscribe(propagator); + } +} + +export const cellView = (cell: Cell) => new CellView(cell); + +export const constant = (value: T): CellView => cellView(cell(value)); + +export const getContent = ( + cell: AnyCell +): T => cell.content; + +export const task = (poll: () => void) => ({poll}); + +const lift = ( + fn: (...args: Array) => any, +) => (...cells: Array>): Cancellable => { + const output = cells.pop(); + if (!(output instanceof Cell)) { + throw new TypeError("Last argument must be a writeable cell"); + } + + const lifted = task(() => { + const result = fn(...cells.map(getContent)) + output.send(result); + }); + + scheduler.enqueue([lifted]); + + const cancel = subscribeAll(cells, lifted); + + return {cancel}; +} + +export const add = lift((a: number, b: number) => a + b); +export const sub = lift((a: number, b: number) => a - b); +export const mul = lift((a: number, b: number) => a * b); +export const div = lift((a: number, b: number) => a / b); + +export const sink = ( + cell: AnyCell, + callback: (value: T) => void +) => { + return cell.subscribe(task(() => { + callback(cell.content); + })); +} + +const batched = ( + job: () => void, + schedule: (job: () => void) => void +) => { + let isScheduled = false; + + const perform = () => { + isScheduled = false; + job(); + } + + return () => { + if (!isScheduled) { + isScheduled = true; + schedule(perform); + } + } +} + +/** Render changes froma cell on next frame */ +export const render = ( + cell: AnyCell, + callback: (value: T) => void +) => { + const batchedRender = batched( + () => callback(cell.content), + requestAnimationFrame + ) + return sink(cell, batchedRender); +} \ No newline at end of file diff --git a/sketches/2024-06-18-propagator-ui/tsconfig.json b/sketches/2024-06-18-propagator-ui/tsconfig.json new file mode 100644 index 000000000..ba47283b7 --- /dev/null +++ b/sketches/2024-06-18-propagator-ui/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../typescript/tsconfig.base.json", + "compilerOptions": { + "lib": ["ESNext", "dom", "dom.iterable"], + "target": "ESNext", + "outDir": "./lib", + "rootDir": "./src", + "strict": false, + "useDefineForClassFields": false, + "paths": { + }, + "types": [] + }, + "include": [ + "src/**/*", + ] + } + \ No newline at end of file From a58225af8100e6b88857ee2caa02d8d18e379494 Mon Sep 17 00:00:00 2001 From: Gordon Brander Date: Sat, 22 Jun 2024 11:15:58 -0700 Subject: [PATCH 2/4] Roll up cancels --- sketches/2024-06-18-propagator-ui/src/main.ts | 19 ++++-- .../src/propagators.ts | 66 ++++++++++++++----- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/sketches/2024-06-18-propagator-ui/src/main.ts b/sketches/2024-06-18-propagator-ui/src/main.ts index b1b0de0eb..0cf48e23a 100644 --- a/sketches/2024-06-18-propagator-ui/src/main.ts +++ b/sketches/2024-06-18-propagator-ui/src/main.ts @@ -1,4 +1,13 @@ -import {Cell, cell, constant, sink, mul, sub, div} from './propagators.js'; +import { + Cell, + cell, + constant, + cancellable, + sink, + mul, + sub, + div +} from './propagators.js'; function fahrenheitToCelsius(f: Cell, c: Cell) { const thirtyTwo = constant(32); @@ -7,9 +16,11 @@ function fahrenheitToCelsius(f: Cell, c: Cell) { const fMin32 = cell(0); const cMult9 = cell(0); - sub(f, thirtyTwo, fMin32); - mul(fMin32, five, cMult9); - div(cMult9, nine, c); + return cancellable( + sub(f, thirtyTwo, fMin32), + mul(fMin32, five, cMult9), + div(cMult9, nine, c) + ); } const input = cell(0); diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts index fa7ca3a3e..c7a0133cb 100644 --- a/sketches/2024-06-18-propagator-ui/src/propagators.ts +++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts @@ -61,7 +61,7 @@ export type Cancellable = { export type AnyCell = { content: T; - subscribe(propagator: Task): Cancel; + connect(task: Task): Cancellable; } export const isCell = ( @@ -74,19 +74,39 @@ export const isCell = ( ); } -export const subscribeAll = ( - cells: Array>, - propagator: Task -): Cancel => { - const cancels = cells.map(cell => cell.subscribe(propagator)); - return () => { - for (const cancel of cancels) { - cancel(); +/** + * Create a bag to hold cancel functions + * Returns a cancellable. + */ +export const cancellable = ( + ...cancellables: Array +): Cancellable => { + const cancel = () => { + for (const cancellable of cancellables) { + cancellable.cancel(); } } + return {cancel}; +}; + +export const connectAll = ( + cells: Array>, + task: Task +): Cancellable => { + const cancels = cells.map(cell => cell.connect(task)); + return cancellable(...cancels); } +let _cid = 0; + +/** + * Create a unique client ID using a client-side counter. + * DO NOT persist cids. They are only unique for the script lifetime. + */ +export const cid = () => `cid${_cid++}`; + export class Cell implements AnyCell { + cid = cid(); #neighbors: Set = new Set(); #content: T; @@ -106,12 +126,13 @@ export class Cell implements AnyCell { } } - subscribe(propagator: Task) { + connect(propagator: Task) { this.#neighbors.add(propagator); scheduler.enqueue([propagator]); - return () => { + const cancel = () => { this.#neighbors.delete(propagator); } + return {cancel}; } } @@ -119,6 +140,7 @@ export const cell = (initial: T) => new Cell(initial); /** A read-only view over a cell */ export class CellView implements AnyCell { + cid = cid(); #cell: Cell; constructor(cell: Cell) { @@ -129,8 +151,8 @@ export class CellView implements AnyCell { return this.#cell.content; } - subscribe(propagator: Task) { - return this.#cell.subscribe(propagator); + connect(propagator: Task) { + return this.#cell.connect(propagator); } } @@ -144,9 +166,15 @@ export const getContent = ( export const task = (poll: () => void) => ({poll}); +export type AnyPropagator = Cancellable & { + cid: string; +} + const lift = ( fn: (...args: Array) => any, -) => (...cells: Array>): Cancellable => { +) => ( + ...cells: Array> +): AnyPropagator => { const output = cells.pop(); if (!(output instanceof Cell)) { throw new TypeError("Last argument must be a writeable cell"); @@ -159,9 +187,12 @@ const lift = ( scheduler.enqueue([lifted]); - const cancel = subscribeAll(cells, lifted); + const {cancel} = connectAll(cells, lifted); - return {cancel}; + return { + cid: cid(), + cancel + }; } export const add = lift((a: number, b: number) => a + b); @@ -169,11 +200,12 @@ export const sub = lift((a: number, b: number) => a - b); export const mul = lift((a: number, b: number) => a * b); export const div = lift((a: number, b: number) => a / b); +/** Call a callback whenever cell contents changes */ export const sink = ( cell: AnyCell, callback: (value: T) => void ) => { - return cell.subscribe(task(() => { + return cell.connect(task(() => { callback(cell.content); })); } From 52c2e42b2eacd12d18e000c6d4df96322b41a873 Mon Sep 17 00:00:00 2001 From: Gordon Brander Date: Sat, 22 Jun 2024 11:45:08 -0700 Subject: [PATCH 3/4] Change API to be a bit closer to signals --- .../2024-06-18-propagator-ui/src/propagators.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts index c7a0133cb..353633efa 100644 --- a/sketches/2024-06-18-propagator-ui/src/propagators.ts +++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts @@ -60,7 +60,7 @@ export type Cancellable = { } export type AnyCell = { - content: T; + get(): T; connect(task: Task): Cancellable; } @@ -69,7 +69,7 @@ export const isCell = ( ): value is AnyCell => { return ( value !== null && - Object.hasOwn(value, "content") && + typeof value.get === "function" && typeof value.subscribe === "function" ); } @@ -114,7 +114,7 @@ export class Cell implements AnyCell { this.#content = initial } - get content() { + get() { return this.#content; } @@ -147,8 +147,8 @@ export class CellView implements AnyCell { this.#cell = cell; } - get content() { - return this.#cell.content; + get() { + return this.#cell.get(); } connect(propagator: Task) { @@ -162,7 +162,7 @@ export const constant = (value: T): CellView => cellView(cell(value)); export const getContent = ( cell: AnyCell -): T => cell.content; +): T => cell.get(); export const task = (poll: () => void) => ({poll}); @@ -206,7 +206,7 @@ export const sink = ( callback: (value: T) => void ) => { return cell.connect(task(() => { - callback(cell.content); + callback(cell.get()); })); } @@ -235,7 +235,7 @@ export const render = ( callback: (value: T) => void ) => { const batchedRender = batched( - () => callback(cell.content), + () => callback(cell.get()), requestAnimationFrame ) return sink(cell, batchedRender); From db5c1932a8d1683d9abf79814897c839b4ed44ed Mon Sep 17 00:00:00 2001 From: Gordon Brander Date: Sun, 30 Jun 2024 19:41:51 -0700 Subject: [PATCH 4/4] Fix method name --- sketches/2024-06-18-propagator-ui/src/propagators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sketches/2024-06-18-propagator-ui/src/propagators.ts b/sketches/2024-06-18-propagator-ui/src/propagators.ts index 353633efa..ab3d7d00f 100644 --- a/sketches/2024-06-18-propagator-ui/src/propagators.ts +++ b/sketches/2024-06-18-propagator-ui/src/propagators.ts @@ -70,7 +70,7 @@ export const isCell = ( return ( value !== null && typeof value.get === "function" && - typeof value.subscribe === "function" + typeof value.connect === "function" ); }