{"_id":"@alphaflow/resource","_rev":"276-ba6fc73989cc7830417d9cf3fe476760","name":"@alphaflow/resource","dist-tags":{"latest":"1.0.0-alpha.32","preview":"0.0.0-preview-f15713a"},"versions":{"1.0.0-alpha.0":{"name":"@alphaflow/resource","version":"1.0.0-alpha.0","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.0","_nodeVersion":"10.16.3","_npmVersion":"6.9.0","dist":{"integrity":"sha512-BzTQGszfpht/8Mrtu2f9/BdTN5w9m0cuD+Dz+oBWyqTvltSjzTR5tTys/OZfF/DQYhEMkamGmk6TF1CqQM+yKw==","shasum":"d6b90e4a82a108d80fe9b9427a3c18edfc3521a2","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.0.tgz","fileCount":4,"unpackedSize":34190,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdp7zPCRA9TVsSAnZWagAAGfMQAIG3/1sglZ71c5lK7Z6N\nfDAf1ynRSfRbbNrrl0sj5TniEBE7kmiouo3Mqf5HsaUzo7I/iSLsPloUHcEH\nUDMx5vh9ByWyivwgq08H8aeBy01I2EqgBFI881N8mjv/0MWC0MqtS8pZ8PNi\noyHNztSQ8x7CX0wc4IKL0wRV0UWkP6YiG/sLNOkO6HYIIT/19j4j6Pu9bmBx\nniAudRTETEvYrziypEYtBrr2apR2sTlY/J4ZXtfHIE/QINc551D7AgcROPwy\nkNsEoDw7yfXIkCPM+UZ+leoi9FnpR+28frjuNWc6etafQFnf5T3DRG4cKwHP\ntOabERMzNJNsPomA/tUhcGmEAiHzOaiLoSL6VdWzTbAFhnVZmrf+kt4GrdVa\nAZFNlwQtGv3t8HA4ZzvHQOFPDbi4tLq77YRBhPRS7/cwbPOyqcZhVpQAke8Z\nLv7c+SWGxU5w4FzG2GJhbIGURl/Zz0zqgkWUh1cpGUkVCwCjvdIUX3WJ4Uuj\nsbXsKunLf3lg44mHK1P2/4BpdgPJfkKNDyI675hFmg/Dpz5C62oFogtnrB1t\nXsMj6ZBfsqSbg/dWq2CP3TnCLiR+87cOw9hY/7FuE6IEj0P+y8kw0SgoLUrF\n89ignjvL4cGN+fyWwEYT2Tdegeah67eMA7GLXtTUi/xVo12TArEwTcmSgB62\noGYt\r\n=4dGi\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC7L6oAm0bwxFw+jU1eryQSijcCIZJSK+eC+kVEuWP67wIhAI7d2zctNDe6wBweeW24qd5L+Hu9UeNqcHFfn3RT0YQf"}]},"maintainers":[{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"ddoan","email":"doug@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.0_1571273935043_0.37148961430235916"},"_hasShrinkwrap":false},"1.0.0-alpha.1":{"name":"@alphaflow/resource","version":"1.0.0-alpha.1","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.1","_nodeVersion":"10.16.3","_npmVersion":"6.9.0","dist":{"integrity":"sha512-8RBQbqzhT5AtWlnilnkdw62WI7qUxMOOJct4Z6MiwxyQLWTcPAqX37ci/fUpR5+Ho+KByz0OHeRniV/N3vmwcw==","shasum":"ec5045591176eb823cf53d0a0686cbd2fc16089c","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.1.tgz","fileCount":4,"unpackedSize":34589,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdsJLsCRA9TVsSAnZWagAAM8YP/20KcTw5d496tcjnDmX0\nSwO2vun3Nxx+pon4BOsqxHWaGh+5FxNqT5cLR86lkpLq10afNBtJXcYk4NIA\nu5E5YKHFUqmHciUPyogB3rlindvhC2ccrmWteQeVgdVJwIXpEtI/r3wrSS0F\n8Dh5i1uBW7K4zVKRNpPIcascfT/Gsu4uVWyl7NrAgXKPcXKqhbuQLYMweoqA\n2mIDPK9Rg93TDgCW6pzA2b252QK9yXycRT5vYklQJNI36EvkbRqeTZNppG50\nMUfR+mtUI18gVdUBzVG31y4TT6fML4oHX2EEGekCivk0zluC2Rk12NV+/Zsv\nchjJoMuNDmqVs5neaoHPTA6YxMCxpo2LTjdX/bBBSQkaTSS5pu1LzLwyAk43\n50CsH2CP+wUdmpwT5SGJrnrkHYahXJiXOkeuODzMjyU4w0zcLfMcWkOC7Enc\nQ3Tl25Ah0Wyt6ppIOp9nA9Lse4gBD/6DwYJDdgDMQ8yXXenvTzjzQk4E794/\n1ZmVHJvDXWLsy3kp9M5nZiSuxbqarn2EIkSdwJyWs0w83iEvjtQmk0iJhM2+\ndtDBXV+ziKjCOvmI4DbCrC8eneDaCDa8Y2B4/8iL0Bpv3WfHgaq8dAFQXAz0\nyxDMRQ13j/GEb9ZAI3xDp3/1/PYfOj+MboJ5cw7TojjCNCm0JMhab5GhYSh6\nvE0t\r\n=2TDB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD+mC4tYsvzT4wfaVSZbsRCBznRO7/hNhha8GBdjVB/EgIgWhohy5nzhxMSdec8+KCP93YMeM4u3cNEMK2oQUIJeOA="}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.1_1571853036241_0.02875427458479507"},"_hasShrinkwrap":false},"1.0.0-alpha.2":{"name":"@alphaflow/resource","version":"1.0.0-alpha.2","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.2","_nodeVersion":"10.16.3","_npmVersion":"6.9.0","dist":{"integrity":"sha512-6Fzmh61eGuIGe5oMIqEyviYhZrqzHbqJQruMIFO13em1eUrypRrtYaJCJsqYyX+mmc5P2YuqneLYsJM+s+5uZg==","shasum":"e0386c3afa8ca42a0ff98266a028c3024b9a4e80","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.2.tgz","fileCount":4,"unpackedSize":34707,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdsKf+CRA9TVsSAnZWagAAWjYP/j0/RBN6+Ht3Up6fvRwy\n3+knbQCEPQsZ9w7IRtLDo0kr4MFs0KUoJLr3DPv5TZFesydLfRFaqEni/kty\n4WhZqGAbDuSIXM0oaCj/dArAT/rPXk7zIA0AARKgmIOjzKzM/qO/jghBoPjn\nraBfo209eSKysvaAHOc1dvYHcCxvwQDHb5i/nSxVPQg+VhzNwoahzdIqNURe\ntwlZ2mrJWKAmGTdGduWzjXaDFuK4Kgqrf1MKpupxKPAemzWAB+vMxJiKKLDD\nkcyTb4TZxPAF8EoRplUxE5E7Duqx54JRRdraQHEfs0w5BlC61IOUQCPU1chz\nq+ZkuZLs5G5cnp2X/AoRCiT5/IaPX1jH2gnw+nBjO6nDxEZzdTshK3jRY6Ep\nQNBzL7MEQJe18Xk2y1DpIyK1sXsk3CftARnT/P09z1LzgN6Xk8SUxOrjzkjn\nayerkohA6O1W9Vmx6SFbQRjO5yJcFSAzljXpOSK9F1eov3fujx+vhpyzEhq1\nWIAFDWppl4QfkrAFOr1197tTRf2by8ECVDekWTzgTr4diXHtX52iQGAbmBGf\nFuaNGB3QOfges8dfO6aAlUEAcJ3Byg8UibbotU8PXZb9pmaNAZbcY8VRHexv\nOpAZQuWIsyqyI+b3eJL2a7M4v9pVUmdXmspzV+3Zl2RNllC7mFZ/zs457lnR\nNPfP\r\n=RmZU\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIC0XGdSiEIS7WIHNsXO4nHFyPt/GtzeOgk+2VlShYX0pAiBdQ7c0fj01BsUSWmdbI0fef0CVjsLd9aRXMeaNZa4JGg=="}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.2_1571858429736_0.8650803164916057"},"_hasShrinkwrap":false},"1.0.0-alpha.3":{"name":"@alphaflow/resource","version":"1.0.0-alpha.3","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.3","_nodeVersion":"10.16.3","_npmVersion":"6.9.0","dist":{"integrity":"sha512-jhXTaECioZuXgq+VjMoJl6RVApPITAOjzAzM52OdEF2NMF/4mXMb0vkA5xCkp62M6fEZ78+dUHL/tD966Zp52w==","shasum":"cdc4fe294fdb8a85dc111d87c69311d99277453f","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.3.tgz","fileCount":4,"unpackedSize":53334,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJd5zxnCRA9TVsSAnZWagAA/YAP/inBEeAW6hxJ9d7hEB76\nsaiOYJ2aNet2TNEYyp9wHoLd58IsjeBRZOU4TSoaKlC5dAR6SY6JnAOAAyVw\n9uQ2StHKw57hPailHWCQ40vUJv30A71loexc9N2Ch9NyqADMhbPbcWRfh/4Y\nk4euRwqsECgw7Y4KVkTgzVumDj6V3tkEmdI10QUHpniiXm0ujJmiiJxH9Py8\n9f/OvyH2dhpL1/Vf9PTnT6M2VgAxHUsjFhFwjd1mcXKaMq/bln+I6C61yLGt\nLjjZZZKLb6J6UHr1rQpAjasYB7llb3B3K+d19/dipXJgW9Ac0eUZASvKGgse\nZrjSu2K9Qp+2EpkckBjhpK4WvEdrZpAzwjLzBQIrun774Banyv/zNLzd9XJf\ngM6qu2J2w7IR7CuJMVCUu6MRuu/FcGZFF7afc8yv449nO62T0xZ6+i7akfKu\nPcih/YToLYvXltX/K2wtGxOq7Df/JRkFMV4NOEwxCqZwJY6M2NznhyL6ZrNY\nG/LQKznc/ux5tsgp9NKpXSdpRcy2TSucTir9Xx8wP6wVOIeozWz3A1SynmgB\nckjiLBOPebuLgzSHA32R3/aI5IKxbwgEkHbAAhwWt3rVzgvTb2hW6n1Tdaoa\n2/l2ajDkzXLOarKs78ylFZEkqeVJQ1BbwM4Omd1Z0J7eRMhq3eSdi1YSdmi9\niL9s\r\n=7LX6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCCIitvxt8n1mviYUA7P9PC5v7x1y6FvBzXiLlm75+APAIhAIAYNooYHmfom3I7haoAzHSrBuD7JvuoNZ9lQ7moQ5iY"}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.3_1575435366921_0.6243333529826116"},"_hasShrinkwrap":false},"1.0.0-alpha.4":{"name":"@alphaflow/resource","version":"1.0.0-alpha.4","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.4","_nodeVersion":"10.17.0","_npmVersion":"6.11.3","dist":{"integrity":"sha512-jzXO65kmKPy7W00KoOzsugtgROS0ieJsMVG9F0+7yQ+0rh4W3nZN/30tPbLDsAPYXQC881jfdXBv3onYzxIaUQ==","shasum":"379f3631dfd052b529a86efef284c8a79cdd7dd4","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.4.tgz","fileCount":4,"unpackedSize":53495,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJd6VHzCRA9TVsSAnZWagAAUj0P/2SBZw7nL2HxN4yFjZ37\nhsH66VPnwTC197yi1XEZ6VnevGhPa/2baZXG9LV4mIINnOMXJnmxsPTiAVi9\nugtYYRaJE9weGCTW+aa2yuaAMSP8WoNWW2GCdaiHdnCKJyo7b0yw1Am29ttm\nir6Cc3JT/Ze4aQHDLWYjYUZeeiWJnN/QH3fWyIcIiuto3TFOqp2TUEeslOSb\nLr13Qv56W7bkRhePGsGlb6NNNhQd9ShPusPFbR+7Gwddn9UKlNxnz/GV43Ow\n5D5CMqdPdROhhXvU3il/wAEM0tciKpa1r6pZcLdPba1b9dNTndvkgXEuKWak\nzGAzhfpAddmDZVjIDiiwpcof8MxAN6dPxaoFVfct6tuNx5Nss38eI0l8UGGm\naNB7W5aAavtBXhPKDfQa55WdWvcmCSfx7JwgFIX/srevn0LSh4BEIdnbOnM4\n35Td7UaQxxmdu30g1kkybL+BbUpo2P5+Bmvm6gp6CRwgoi+m6vHr7Nu+eayT\nGLczbyhzEFq197nQlEHl9dEWsxyK0f84rJb52O60Nf3cpqnJm55b6OWVz8bC\n/dR1EhruFhecA9zhFwVRVklKc6B23irA3G6mscJxNjtwveRhSMS8W+5SzLYR\nMRFvawrFIv0tcQdBfW4m+laoMCjBXcHlWDmJhJghup5hwi1kvPZslG0sEDvA\nO9DX\r\n=IAUB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCvsZHpMiYIynphF1JTOs0IDlOoXjYIxKKstG836eGMPAIhAMFxmNJU9V+Pje5zMcvBnTAXqKS8JjgNemYJPQBgyW82"}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.4_1575571955107_0.6019845077970725"},"_hasShrinkwrap":false},"1.0.0-alpha.5":{"name":"@alphaflow/resource","version":"1.0.0-alpha.5","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.5","_nodeVersion":"10.18.0","_npmVersion":"6.13.4","dist":{"integrity":"sha512-ok3k2s2m9zCMrpqZSv3qaF7Y3nmBaKtzFq6KbP0Oub/z1JfOJF1FBZXZGm8pfYZrInLFCYffL61MHqRL1afTJg==","shasum":"42e854e7d1669729802d9c5a856263cb40546219","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.5.tgz","fileCount":4,"unpackedSize":55052,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeDpmiCRA9TVsSAnZWagAANjoP/ji2ciaVbHtBmxsBOblf\nH+TlexhbmElr4TN0qOdUSL2dBas6hi4tUU2HzvnP+PvOUzwJtTCPY92HUqPn\nAoHH2cY9RwJ1JnZD9ttyApi36eE/HK6Y1Vy68X34wWh+ahhncx9L10v/OX1E\n+cYZxU0bEIl52gQSk6P4XWb151/qUJFzFZQ7h50UrVmSGrBaOmcS9Lmno8Ji\nqwawGzfGBIWqAL3EvcycHmPHyB4o3F9yu4xWNeiZzkGRJ6nBeoHUWXSUx6oY\n15TI47wocu0+3nsRR+jjKITro+FVGon/I/0B3peY5GOLK2j0ucwfzvYzPRmM\nh4+ber/4IfXmpcYR2zvvcO5mwGE4wAPBfCtxtotDXtfX1JNzcRj+LaDmK0X7\n4w/Kn7E74VF8z2WK3Qn5Yxv2fiyMzTSows+M+Y0Bw/8mkQlGqhmnB4da2z/1\nyQXG5rjj+SybgRt+UF8WY6Vo0amHSO7LbY0RbH26+S5k//5NjK8F/ECkc0Wx\nqVfitd+RFB6MaGSsfj8L97/ODAz6Y2ATT3sDiWGTCbeGI1JqpGPkmBV5ahNY\n+WxdEJIH6EiQFnCiQb6fjqdMjUQNG2AzxXnzjgCPwFV7xhPhJm1PgKCgvkoC\nZ0uJtVNH2q/oDuwRYXKxChM2VgnSkm1winlafPiK1QJyT9Z6DcpE+Pma8pAf\nJZ1h\r\n=G+tR\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAE9MTn1+4GnNAqxpxGVUYM3ys9wijPAYPLr6kL2AC7cAiBIiQsMtmIBO6Hsifp9vMZaumbC+TbP3t//hVHHPv5FOw=="}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.5_1578015137599_0.17913699674438988"},"_hasShrinkwrap":false},"1.0.0-alpha.6":{"name":"@alphaflow/resource","version":"1.0.0-alpha.6","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.6","_nodeVersion":"10.18.0","_npmVersion":"6.13.4","dist":{"integrity":"sha512-nmwzEUwWcKODjFnc481J3hY8eSQmcOYwf3dgPnD/6XM80HMQkztIR/Xi/Tcsy97Pqf69xQtDuRTkPHr6wSroEg==","shasum":"f44486fae76cd8c86ca8cc9fb4b09cfa3f0b0b58","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.6.tgz","fileCount":4,"unpackedSize":59103,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeITWbCRA9TVsSAnZWagAAkAEP/1VV2tdtrURnG449Ks3p\nBRVcAe0AAHuTE1xxwaKiJaTPtSOr7rJ4h6em1oh/42hRCmgAMj47w0/ols/4\nNNlr1wCHCD6xIsHygnde2VLmUsv7u4afrUEH8z2Mmg8B/1dzTIA9P7udc0ql\nMuYIH6oT2hgbF+dbxx480P2oKqmQ8i2oxfhkoeVjPRhWBTGxTC4uewGhdHdZ\nn3mtWZmCDz0tD6neY4nfq++lR3BEx0KzZ9tHV32t+mzS2aHoZxnxOk4eTl4R\nsYTGLQbZNyK9vDUbe7n1F2Zxa9y+GQ20Nu5JqoKztqCeFdusdu5nrlwpUKiE\nSLKVCurvB12IHCULlTRS08fZ5sTsa/tcAVtEKm0uJ3D2f5LWq+TUhamUOZgv\nKfjlycu1rnDR6ACutgjL8psrTMcsIRJtP8FrZqPtub4sh+EqZ4MvdhV/6V9j\nFAn9M2SKfEO+ZgIkxpUgYsohfnNdSOFUHD0qkjrDgZVHZpqQBy74gVWo1D/5\nJgFmROvIp+2Ov8mrz82hdxLbg2CFZ7oxagqgBH8twEbNk7h/IVzNBZCtc7Tn\nxqJdUVbcfnUyYWN6Wev1ONima/R1BFZMM35YGjnT+MGKp/+nziSJcTrXwhrT\n8o9hElw88qDvKmmMnSs+yFW3l+hEpwawMpXULivRlZr9qthuTC0XSQlI/HK8\nLCGF\r\n=JNoX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEOJoC6LhnjGWqCjalBvW0Y8BltiznyNkp6GcgiwMb/HAiAH0QLU6zzAYjPWwAVYIUPPCMYmEl42jice/FDFFaHGcw=="}]},"maintainers":[{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"ddoan","email":"doug@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"smundro-alpha","email":"steve@alphaflow.com"}],"_npmUser":{"name":"jwonsever","email":"jwonsever@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.6_1579234714582_0.9550276643106685"},"_hasShrinkwrap":false},"1.0.0-alpha.7":{"name":"@alphaflow/resource","version":"1.0.0-alpha.7","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","enzyme":"3.10.0","enzyme-adapter-react-16":"1.14.0","jest":"24.8.0","matched":"4.0.0","react":"16.9.0","react-dom":"16.9.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.15","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.7","_nodeVersion":"10.18.0","_npmVersion":"6.13.4","dist":{"integrity":"sha512-4rlAgQ0sMh55HMLx/etxqciv/QlhfeoF9rrnxgWLmQGsPe58553YrQAQmaHvly6i+jddrN5uktFmsMUwssC8UQ==","shasum":"47c742d44f6f4f28346560e2a989a16c477025ef","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.7.tgz","fileCount":4,"unpackedSize":65588,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfEKpHCRA9TVsSAnZWagAAqKoP/3TperV15H93i0rEy+dK\n5LIBy56fugEIB7dXhplPTPZJsGGX1fpphRQTB8BFvI7KpIkyp/1d1OzO4YTC\nPqu2HF/JJ9X2ss7tmgjY7KHqPe24wSOZ6RR6uzVyTMXn6mc5Rt/EgwqMqHT+\nwAwaRrTAV3B4ZyUDlL0Ofof6UG0YWuEYS6bUobzGkKuje3BO0B8Z80lbKSgM\nijAGezOGsjKnqFC5awHR8TiuXhJtiDpPEIlpk6hDQS+078GxVfWtFTPh1wpC\n7hnZ6iOchNcK7l5EZi8NXREWtT0TqMMhMM8LEaxTotHvr1mw360ytQrVHwI0\nEkO4w+zxbBpHxZrr3nCa9wuVsRpfGgX/nFMiViiZhFB2B4zB7uG0gKwdDg79\nYlZvqJ+U3N2/NpN9ArugkiuMpcnEJWFsypPm5KToYgTJAFhsabDiqmiWSNSS\nnd8f9cJsxwv0W5wcwX19etapVHtVuGXT3FfgEs+plrYOQ8Sr7PCpLdy43IOc\nHTwkXoOSWIehaloAhjbq2yY5/YkynRyn6TklYNuRqJGWh8abbpwBTBF9MyKW\nFzH+PkuHhTL4YcVoRzGJVeBJFNeGfbqSmPoQr916PDa5/lBZkdllwv7DFUcN\nSMd9nomeqTw3Vrfda+UrHCNL2Yqk2DWP/QQfmg1sPxNvE1IYd7O5nivPGDeS\ny4Hg\r\n=ua+w\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICO+ajdHNX3zj0H5vld7cyMD6lnHScAt+pAxhSRGkyaPAiAjgk05cypQhkNpvXnuYqdWNy0/2MdHolJqvXeesGLVKQ=="}]},"maintainers":[{"email":"engineering-admin@alphaflow.com","name":"alphaflow-engineering"},{"email":"gus.nordhielm@gmail.com","name":"gnordhielm"},{"email":"jwonsever@gmail.com","name":"jwonsever"},{"email":"mark@bunker5.com","name":"markeissler"},{"email":"michael.mach@alphaflow.com","name":"mike591"},{"email":"nscharfe@gmail.com","name":"nscharfe"}],"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.7_1594927687054_0.1000152082555188"},"_hasShrinkwrap":false},"1.0.0-alpha.8":{"name":"@alphaflow/resource","version":"1.0.0-alpha.8","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.9","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.8","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-PKjr8qu2uFBaOf5pd9bJpLUhFsFEbo8CeangQf1vWg3iWgnKZMnswlc8BZiPFwbT0bk/xpSOHxAoPuVhca5iqg==","shasum":"b7e00a6e6b6f162a7dabb6504b41359a446efc5a","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.8.tgz","fileCount":4,"unpackedSize":66499,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfmd6lCRA9TVsSAnZWagAA5bgP/iQoVBBHIzZPiDLCs54o\nGzkUXRw0evgB8q1FXsEk3mDv2dy9fMyvWArlpXE/ao+04rL5YTDTOp35Pjct\nRneOjx+rcYiqQdK/lx5hcE4IHhddDgI9Dqkv7K8lIY5qktFbKuj7U5XSX3/1\nvEUisrzJr+xXV0KSXcKidogcr8w6R8kSEYPbNjlfL8yCE5nEXn60qQRNTayX\nUwLXAiyA37tbKn97jHJ2xi580bEfn2+2eh4YF+x6apIJS8jgzlPN5KjoQVUY\n7zQlKbIa2i5sGolj+4cez/kcrI2ECBQAOWNfPsP1FUhSzPzBIMfHnXg2xD7T\n6cnEb+mbVqySeHbJr5eTUqa6Ip+xRT9rCgpOEk7pa18dYl8ORykeNJzt/rsQ\nagyq3KxVq9ZZGg8wZEa/W8XBch0DixVauPPgAjcsntWFsB9+EjpiImmPOke0\nPFxowQFYtYzyUp3TT9vyT1z58AZdy5LDQg1+Zc8GZkvOBTEo2H3aRLurOT+X\nocSQ8SWIyATNFViyN9ZgpPv2mo4I+EAkgaBoMZTKFmr/vXcXHa3CSEMOZlU3\nB/NGfEA6P97XOtTPqssc56B6cKcazL/B2W9uI/s71xDxLkjtLeS/zXx0djFX\n3gnBLMpDt+3ve2ugzKomcwEueLG0hDUbYI3/0onbXKJJGGHT4CFTTWPseNom\nMXEZ\r\n=u2ok\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDWVZXsyI0bYvd9u9zIfJwmEx08dtP/k8ivDM7uq7i46AiEA8CPRpa9s7OmPNoh8cp9H+AJpDZLTcWvlh/yhtdJKaCA="}]},"maintainers":[{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"}],"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.8_1603919524544_0.01381672933317124"},"_hasShrinkwrap":false},"1.0.0-alpha.9":{"name":"@alphaflow/resource","version":"1.0.0-alpha.9","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.9","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-OrZUHluJGjqaOqcaJ+sEm5HinAfFZXQij/sDBycI7W6ZJXz+N1vTgnOId4x4RKEB1s/v8GIKWX8/QyKagdgD+A==","shasum":"b9e101b9c72622e913ed4a67e61d451c16d90b13","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.9.tgz","fileCount":4,"unpackedSize":135917,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf19YcCRA9TVsSAnZWagAAMdwP/R4D3wTHSWW2cGCRpnjq\nv5P+pVp0ZMuI+deuYM/tDbEJQysP4hvXI95THgx2FKL3i9X/A1583jckfn6F\nOael8062dgB+OLDWOX0VmrpAb5cY9uC1bgTwsb2PExTKaxKb/pu27A/ZDBtQ\nUsPdB0hPcMn03MSe3dxOL29WxhzcrJ2lz73+p7bggsA9MCmsw2ZnRB4oRs9I\nHmpJRRh0R/jWQDpmWKYanOMqr3ijl1ef8axWGu4a7AsDY6WuNW/r5ZSESQax\nHKwT0BVsjzSqwjR3sZ6WW4bYhDSxqNsqcsazp4zeEJtYvJ8btw+CYis5+++T\nXLtIWkkQwGHscR5VTQxXdjszu+ga6NYf1Pm674JcfBQCHBoaJc4eJRtMTwCY\nFFs2R0eaFFVjUKerH5fKHuftE21M6GMhq9gxm8ArRFHFfslFwCOWu9TDts0v\nTL80KpGYhmVpgKDd3Iw4emQezoqaEYD4TFtSOOqRANLsN+4auGVD+LKqwg8T\noP/kYdMJ0EPwVDWke1EBOhbz03pVlA9F3DBuL9fp+4yAqy+HSwyY9Nb9AAMY\nhiFDLh6wQI54/eaD8rVBCZfDeWd+v8myibUy7XgfAFU0X02G+bZSxtTjTTtb\nRmv4LDlgTR/eNNeq8//UxmZQGkPmApengxlqO8LhbQu/wmv2f7/3n98G0DYj\nsayK\r\n=tAQB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHl3SEl0hk2sVHTYPYy1WFauU8DCZ3byH7bKvJeDDA8WAiEAlOXWFif+k6aeAWrA/MZVXbVa1ZWFfx6lwIW0876qW2M="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.9_1607980572215_0.8892195229221218"},"_hasShrinkwrap":false},"1.0.0-alpha.10":{"name":"@alphaflow/resource","version":"1.0.0-alpha.10","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"1.14.4","rollup-plugin-babel":"4.3.2","rollup-plugin-commonjs":"10.0.0","rollup-plugin-node-resolve":"5.0.1","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.10","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-WrfSOxjwd5QG+TXF/UyYhf9t0NXAGs7OlR6Hhg9ecT5iTTYUHplFNJiMQkVtD7IBSkSR6LdSGAxDk7rgeC5YPQ==","shasum":"2d00dde06c91b0b3e8058a1cdc4b37fa0c63d4d3","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.10.tgz","fileCount":4,"unpackedSize":138433,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf29B6CRA9TVsSAnZWagAAsfoP/2OJp3dXrZVQDRNyxhaD\nw4B16kMS5cIU60YqgPEWHm7Ql2dzsRbHV51XwsJFc7ewzt5aZdFQpz29m1rH\nLC4nyhtovfN324fOcT224ryqq+2VrB4e7ui8kEOLwWr+bfQKvNJnlt9V/tFa\nWU8wFsK4nUl5ZSDiVEfylkAs7tJfVxTlJPq3PPtzdVOHCreWGr8lXEksZW2W\nfV5wLL8L+7BXLlh2iHEiN+AaC30e5I1UuPTZKgsa2Ngx09zCi2Y5Hjzk4/bR\nHeqeetZS/3k3J1qDMnnrVGSmy6yOm2BSxj3Dc8NWmwJZTWKdMaPPjORETvrM\nqeBZQL9bmcHksqNVGbiSZRSvFTZ/ZNHLMJSO0Th1TWTI/Z9ZKlyosKrbTd/t\nRKauSX8P1bZcrFkDKWK7Txq/mIEIRmCMFm7RPADsNLGhhc7uzLpt5HDr1hcp\nlNc3y1M3EDSma/jSUXkN2pZjaPW4Deo/uQVhWM8ACFWzG+PNvAg4AOOU4xlZ\n8O1e6dum3fTp7yns8eyLxJ2cjv/APZAWKHJncog9S76cOkmHZKs+RDIYTT2q\nGOdYHAxs7PLnInw6qxoR06dFn6mWa9OFsfrFHXG886ooLe3hMp2BdSJG/v9f\nNB8py11RHUaSYz8Z0/oK4T0StXZgLqayxtCt018P1IJon8j1116ixEOCredl\ngxV4\r\n=86bK\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDHv9kaZDbGMkDPL8jt9BpstBFZm0rd+D7VDhfb3GQ2NQIhAKOfhbvNVHtEs06vJEaZF7LGkEkq/47XCy0Vi6HXoN/C"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.10_1608241273870_0.6564483786440283"},"_hasShrinkwrap":false},"1.0.0-alpha.11":{"name":"@alphaflow/resource","version":"1.0.0-alpha.11","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.11","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-nl0EfaJpmkgWjb+POrpQ0lb+MYyQigexEjZUagdl0ZPDPDdqIVw2FWiETyPScQnlXqhUv7482kf4JEQWr0EEgA==","shasum":"fb68e5711443caa12bdbe7d2797e2272cae6f1a2","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.11.tgz","fileCount":6,"unpackedSize":427117,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf82YyCRA9TVsSAnZWagAAL1IQAJJeJlcsPqDZ5ggTEZFG\n5flCkEz0v79numXUeKv80WmDJECjggxuiiR40VUQCNowDjkt0eTKl8wc/QfN\nLyinQuYM8LfBYMEpFnr5/eE7xzJhYMmtWJ806Ddo6SlcJw7TXSZk1BwzDfcm\ngjWkdGZeU7aUbSpJgfZiHcAvj8hp0uPi/ZMhTnFjbx/AzRV189FbVeHPrO++\nFzxzseJGClSMAJ25TrkKJHxv6q3bP15G7BWL9fZRUqMpPCcGxr56oqngcHUT\nnJ/BUsaWWIv2cSGgGkMmjRvYNwwRSxW7JQsOurTrDc3VdBm4Vvybvvzq+kqR\n+BP1jlIDq+U9aJ8g8IciumbQ/I5eeEtud7mzvD/9eZTYqYw7J+ybIhwiuAaW\nMeaojV3v3i6P7SAdtFp8z5XfrrgvXKH84lEnZAimgvJhIZxffS0Y70lyJGV5\nCE+xz3plxbRkXpUd0PcKn2aU1iZk75bYa1OQ6q2tkH/JV7CIDB3Ytk265/Ap\nIpZ4/+Kxn6Gcu1NP/r23lnYOgzBX5lEksiFJUQNUQkB12YbVF5odezD5lTqk\nIzC0lOdVN8PZM1JN5y0ZTuP5ZzEdrafYeSRI3cFmDc5QkZDPR2yyS9+yaxoP\nLPVB0GOrZhKTw16nWPUowBGk9dWbtBbt99602ehNvOX+auBfbOJxUatrChJE\n0QHh\r\n=sCQ5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHtWWGJWFsg1YQGopoCnrleOq1HIkxFcVNtysDu7fRn3AiAulVPgouEuBKB2TkeHJGTcjkMYVsMjf3sPBoVBQvFd0Q=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.11_1609786930133_0.04772139871217029"},"_hasShrinkwrap":false},"1.0.0-alpha.12":{"name":"@alphaflow/resource","version":"1.0.0-alpha.12","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.12","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-6NT98eZhskU0k5fM02AJmXPjJ1oyn93yBju2GQS27YpNYwRCIVP1AyxtgMo5YL2s7q+YRYzEKrnUhnHRVdnO9Q==","shasum":"9832cdc88e2dc0604890a91432f6d1fc171194e6","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.12.tgz","fileCount":6,"unpackedSize":428558,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf9LMaCRA9TVsSAnZWagAAFsEQAJpRP1DRic27z+kbe65J\nh1iyWz1AneWZBCo9bP3khJMKAXPmuoQl69FZc7zjaJBREdao7WMCBRAzDUKd\ndykM5LeP1vfKRzNpMSWAYor0JjiEzWLrNHEYfy98IvtziUvV5i/1zKuV7hM0\nyOGw36vV7hF5JkTQy3Oc+GEw3Ty89bXQZTmTLIqeq1X1LmCXclabBWa+NISL\nLzwpkvc0Vfkqjxc9wuwfGIaZ8WGTrKOiR5tofX7PLrw7Czj8o1FtYki1AO0I\nqZdIUvKKlRsJZM34pl7A1R3XbM6LP/QOr87A4RT45UepNKWk6i4fO7TaYioq\nhTUF0xkv8AgGfoi+G1wtcKg8x/qGHSIKDHcj0NWihGB2kA/wjUKir2of8/Kp\nPt3dlLMG2s25huKzC9Hod6Y0cRN3aOzraGF1rOBe1A04/WgITeBdvB4iuVM8\npm00qidFrW7y4wA7rFgJnPOlSDl62gTDnkO34fRQCgRpTO/+anlPmHBTYKXj\nvKSehLweZQUCC0emYh+AAZnBshnAXx4/iugeLWNtF23WB68EBrT0G8Cog4I3\nUYhxpMaCJuP5LQObF15BhndrXBPJKp0AVP/UmKumJ7jr3/mIxglhJSYZqoe7\nJKv9RFUwBZJlj6QR1k3SlE2mT6mc3GZ9ONjCG0xgctnZsUbD+QDPCyNNXhKb\nWgzD\r\n=iLAW\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBccIkMP7agPBhYEAr3sln55wjZD60xV3l6IJUkC7ppQAiEAhiTbkrBGA1bZCrWcfDeYVu7UUyZiwIFxPvo/OeuaSO0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.12_1609872154219_0.5109623454058556"},"_hasShrinkwrap":false},"1.0.0-alpha.13":{"name":"@alphaflow/resource","version":"1.0.0-alpha.13","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.13","_nodeVersion":"10.22.1","_npmVersion":"6.14.6","dist":{"integrity":"sha512-DLVhMVLnv+vibBcFI1Qe4X8e79XKUN0YhPZlFSYcLCKN6GPcGQRsiFiPFP+l/zq2Kq94wnYjLpsG5MCqU65H8Q==","shasum":"3471f522ba44add7f67b54af510936aa6454da6a","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.13.tgz","fileCount":6,"unpackedSize":431931,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf/hm/CRA9TVsSAnZWagAAYJUP/jtttdDwtvG+4KtpGMf6\n6F81BYI7vKo+WnERJViwnqMyOB7AY0npuqJU6HCT4fqKuSRsX0WOZhnblwo9\nqylCyYJSAsnof4jppEfeSkH7Y1Ndbmv27xTL9/lKQNzzamfhbRahjEGS3/IK\nA12uZILUsL6ASjcaR/IenpXdNaNmuqR4Up76bAWGHD2rTFT8HRUTKPzqndo0\nxzKoBH49VzQjLo7AdwxJjpDjHCF9PTrsQDVH/QzsBusijodpqmAyH0QAe2Vs\nfpxRxSmVjIvP1JXj7kIJ/n+k7aVUNjF9wWvHujcS9ja4uOkR5qhb/Gw7e4Zp\noxutPL1OYdMbvMB+sOlEDQzr1zMnIXBZcUkXczrvZzjOhJZaGzSG5phAk7ux\nHPquMouAlkYwuByEcRiowy3efPXYd0CqFL5c6HJfT64MFpCHW8PXCM+Sypr8\n692vi0HLCaHNKYM9LJ1Onv8Qw3iYst6a8LQtUo1iIw7GAd+ob0XOj3s9HLpu\n0isw8Sp2zo1ySc+WFwJdI8rLkRbYHUjTv//fbqNrcQ5Bn/0LrRjXZSbZIl+K\nvLPoDADN34zBWucQO2cgK1O3QuMHSTOvszcbwf33WNovMtuv/FYcEDUoVEby\nQwLpa9NCKWDVj0WoBwiUpn3oQIZ/rUr8X9FQOYb6pDoUSr9acW2LzfYO+2/z\nR5QR\r\n=HFCj\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDCYGfb+JAHMcbuVuJgBngtvPIutp2dj4KbPKixheP3BwIgbOfchaEnAROXdTsPiyeeLrPI6KakxqKOz5gaD7/OcCA="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.13_1610488253796_0.6116645127860785"},"_hasShrinkwrap":false},"1.0.0-alpha.14":{"name":"@alphaflow/resource","version":"1.0.0-alpha.14","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.14","_nodeVersion":"10.23.1","_npmVersion":"6.14.10","dist":{"integrity":"sha512-Wivi2xA/jd8C7zjfwZHnM7gxZ25QQ3KQOD/6KN8+ktxegdDUqUNOvMZpIjCQZ/ErrrOgFNj8AySGrzNfGuwYdw==","shasum":"bf4648a5f35e3d721b68ab826614efb1f66122ee","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.14.tgz","fileCount":6,"unpackedSize":434526,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgC0YOCRA9TVsSAnZWagAAhRgQAJswhzNtyoR9Q812cKLn\nRhAEswqwIYF2934JniByQ+F87f3tXaSwmH7kqVYppOuR3M+MpS7/WH+5sP3S\nqnKancbx7Ena1C3pJWekI4nNRDN9ORUSj5Dp4kEjbl83FZ0Wn4FtCiymssf/\nXJJQ9IJWufOgwNtMMM0R6hSlyFgxNnVW02Vcre1Cle8SZIcyo2+kA/sIFk6z\nfLxQmodDkc+WDVZBBY9dwe8L8Ic+hvGciRp0wjqBn+kKJjAm1BT7Qci33OYA\ny7YmUL/UYbaZqdHpOSxPf6HU4M/pIgjKW2Qyue4nkR8c5RHGEqOcjNFnx+oJ\nda/+E7bV3JUXZJuj+kbyExRw/YxxvSu0vpnyKGGvE6ECyQwANaofirwP8k2F\nLktyuq/myNvBXI/AcUpXbPbXK4Yc5c6KCdzcoJQB5plddTAEy3eZ3fQuSiYE\nEzS3rtBGRj+9jpZEpVTH+4aPED1pkXgJ7FcgG/jZfYQfObl0a8ycHpVUTYlI\n+jdBszsVC1tam2rKpVD8EYsN/IiG8I1Z3oijFUOXOIEPRjQAKtEpJpheMYXQ\n6pFcRWkGZZwH+G3g5G84W8e+suYsXwwhmAMH61Eq025QBDt0Sw55QybkZmwn\nb+K+zENF+k4g9QbYEUJsUOrvYK38D3wauX+GcIvmgtWfata16WGKlKM1uBM7\nyn8Y\r\n=/7gQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEic/JL0CXVzYoweB+xAToQkiztXXz2XUKbPYS53XaPTAiEAlC7TCREO/mgr9PS8qH/tMU/RjROC6+t2Cn5mE7Pyv5Y="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.14_1611351566537_0.6055387926216815"},"_hasShrinkwrap":false},"1.0.0-alpha.15":{"name":"@alphaflow/resource","version":"1.0.0-alpha.15","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.25","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.15","_nodeVersion":"10.24.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-3yDHVOsU1gl4qiP2+YpBNzWAn60KDHimk5MEkAJG3YxS+erW4tJOdNwKVa75aeaTnqG63eiPna6MWWK35wjidA==","shasum":"f435db8118bf9fea934c182f0081b96857edc874","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.15.tgz","fileCount":6,"unpackedSize":444295,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWsqMCRA9TVsSAnZWagAA9b8P/1L8YIxYXwv2bOVUeiOv\ndiqEfNLZepOmgUrGfpXrvOWUhgX2vn0blyKS7kXx9fvwg8fplfOwaeJKdVdQ\ns7w4Y/vxhYD6wqDBx3RMjvdlX5ualp5V32SsHR7gNsEWmIV+C5ixjjIB+Xsf\nSuVyCAGaix6M8W3jSH934wvrGXy6KD9KretX0fBScleOfg7HGldx8+JBCMlE\na1+pNmwa5KRkT1Z2xsqpv5QwjMkewdMYs/+3rTOrFJN1ORk8Vxnc3OFTH+hV\nyqndGvbWmQEvwYVZHyRW0J1DcEQRdhL1ZaQFI7j1/mAlkLzOnSFD5bWkBORl\nb9GRaA7AWqh4YAmWSDut+ymh7x5xKuhpuXHEQ7bqDgl5ssXgVT8V+2Fp5RQ6\nQEGY/sudKvQmZvWPnDbzbvYHW1juvsMpq0cIRHdfksXYUzWsrXca6lHgpH/O\n4I+232uTPYeMFfpYJObBJO19w+u27OpWQ0t+7zrgZxMlnjcsvMhaS60+Rwg7\noQglQYr82hUm6dKExltBEyf02AR2UEPO7syXUMnGCFq+e4akycxGZYdNAP4S\nAnsptYfzzPXDXtJ+6uu3m5jD6W38UQz4DWT5SoHFpxteRBXeE8g184EiXe7t\ngyQw6IBXjFNorV+xK5IZQVDHMfCrx//NpqBhX0rrKNh7YefiVz+M7FGYJFpg\n4E6w\r\n=EiyB\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC/i8jp7QHQosiYtMfGeYWJ1gLrTzL/scDf7LupaEKq9QIhAME5JK70VTW5HFbxki5Ej4yz3yhhCbEiBC96WeFUta1y"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.15_1616562828172_0.7916892306471595"},"_hasShrinkwrap":false},"0.0.0-preview-88c965f":{"name":"@alphaflow/resource","version":"0.0.0-preview-88c965f","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-88c965f","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-gy8I7PuUFJXH0uWAlu0YWeKPWLkgmySMK2uKntYixdmSAVlzsDIuVVrx5fEb5jauUgwp+Pir0MvY2PKJI/4yvQ==","shasum":"243528f4630bd84afc3b8574389b8eafc23d0d0f","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-88c965f.tgz","fileCount":5,"unpackedSize":442880,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkt7XCRA9TVsSAnZWagAAvKQP/2+V73J1GbP+RBPkVpOP\nts9+/41+3xLFpeGBwoAMDRe7H86kNs2ahvXTcqI2u65gZFBaSNxORCbzNQon\nfPyqn7z9uAxSYtBpp5Qrm0Q+gVKzRiq3fKmilxb6vg9SrZs5G0v+UNu7+O4M\njSoMloKqk9mts9VcuNHH4BzgsL7/o3AvKIRJizqWXDx+yqqdLTCsQZiq8832\nc+3kvzOv/CwCIF9DZ7oObHPgyCWmTpbF9ghFB793PiLRg80r0zRJfcet2Mah\nn3YYhh7U+cjde14hsaratlfU71tRhY+aGnRZxIhRca02R0k5y9vscdjcaHpq\nUFhZCTuRAV6BhqMyCCmTkpLKeQ8VO3X8gNlRmaySwIAWlJ1S20QJ7Nn66UHX\nx2HDW87yfyVKdlbYt8SmIaNkYLsqxASum6PUGyqHA0BNctTBK9S9Q2l1DaJ3\nlNrWsUefJDj3Ut7iOtXDVYay+DGMACTdPsFstQ5K5X4wSX3GmHZaliKHM0Ys\npNl/zeBSRkAsh7IJg9Vvp8FygLam13IQG6HqlNLSAIK/gABUAQIwlO1tYyVh\ndht0R3V+hGs4wo5xblU2W2aLcwJjrtmPwfMkA15s7Uav7zqX/jEwVlWWyrZv\n7AHIL7WlCeDGc7OlvFJi/q23tYcSpo/iGxZUfyWQpClpNH2LeSOPyZX6xrbU\nFRzO\r\n=+WYV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDJfVAj3oj8vnGg8QTWZ5QSNpDrW/GdSXtAb8DDFGt3AwIhAJ9/mcADkM2QZgsHw7mzRnMOYKyVZbnttJj3wnaV9i6Y"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-88c965f_1620238039498_0.14608238914232352"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-ed04cea":{"name":"@alphaflow/resource","version":"0.0.0-preview-ed04cea","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-ed04cea","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-zIziVl2SmBgj8d5YccVoYpZnYb45NB+50VZEK9vqcmPLaZq8bgDPe88aFDtqoh92Wlg2Wx8t0oxa7DAB7OzRBw==","shasum":"ad03a9a616899a7116d90e636b5f88a10ff38ce7","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-ed04cea.tgz","fileCount":5,"unpackedSize":442880,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkt/wCRA9TVsSAnZWagAAz5oQAIqFqRf1FkOfYywkmvng\nheEUVM54+pKnbeO+p32pkxGLmlv1Wbq8fTb46xUSruDCkpn1AH0pOS9YW7Ia\noBAl6JytqItLxa7LDt7h8hn60gb05hbNeAreuJ0ZgU5fZf5b/ozEVTr1THib\nZ+5atynt94R8zSzmIDDR+kG4thtr2Bdq3axb+wZfnCAWSRPPuvhxUifTpo62\n89DyEszKUt0mEaU/UO4egiXIBEFJefVluVZ8mlZwYfOu4cINQKIVI1luhRMN\nlH0FbO1eXW9jiUGQtNbGVFFnltwKFM5eRKCXRSK6hhxjt8r0lHkPpyJY3XmQ\ngpLsUQ0d3pvLrkPf+gdsqSj35DeE4AuTxq0XCzSfnxLA13NSp3TergfHFqwD\nke1u/EhKbT7OvS3LD5EFEOU99xVRj0tUV5kcL7Intde108HO57c0YQczmJ30\nBTq308PAaiIT2Gh4xMnZF4KHH/cQdBFm5pF8+NF2hR9chovrhrBeUSEQ4jnv\ngPLKKBDjmW1t3waeAtGnLIelthxyO5GDo0TGVEjwBOJi3Crc863mVr2fw9do\n+YsTcuzR22ZVnaeI9+OTfO/j4DXTUgecLaSvO6Zfc6RF68w08LpdLRxAh9Vi\nPLzJmXamrV3dg913O+IrEi23hlD1MuxykNDFmhvAJFNCOJdKml4+enT5YrH4\nXRTw\r\n=p5XZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCEyN1mZroyDpwNyiKGY6PMC3vxxPld6Vvqvy6cB0X8TAIhAIA5n6VwSDwaM4KnUBIkfS/a8k7/bcVbrasXzoBL1eVv"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-ed04cea_1620238320263_0.7337923274447722"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-ebfed6c":{"name":"@alphaflow/resource","version":"0.0.0-preview-ebfed6c","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-ebfed6c","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-zdsxUguo59m6GSSJ1ElylbaNo4l5uETj3qgkt+oZLGivmov4bhu1COXFPYmKM9Vpx8FOBO0GjSYSbOZvUUrIQg==","shasum":"5cf1e48d19dfd27e14150b2746bbd4c44a6e94e1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-ebfed6c.tgz","fileCount":5,"unpackedSize":442880,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkvj3CRA9TVsSAnZWagAA4sAP/2xROZ6OhcgyZIaeohPn\nu8cfdo/lwwqmXmpjAfjK0adLoWIGU+InSnwW3iyhNKoubHMvb9KQ5I+lWzqg\n/YQWtTjj20r6UsjG0NJSucGyCDmiwrP3sDOAjKLujF9lzPtVhYoqxNaAFWF0\nFo2jGY5mP15S/AgOHYnaaM7o1SZv6j+4inwqmlPRWqDT+OsEWZ8sETElq0mt\nvpY6Qm3BXff3FgG1J0RZXXyTQLyQk1xix1LiizHUoGTWSGWPDcKTGxcukuHb\nGxzBnyAW3lcUrvG+ZksrW97RwRn+ug8YLYgj5tSQbKnUpDIE1ol2tBFML9N2\nqN2qeoFFcqZvtDXB1J1U2EPwxjCPQNZ37YSTDl092QD9nQo2X2h+m3lojt/t\nEDHBiF6qVDJ9EQZAG6rQ3/yTh/syw3Fwqc+o6r6WVJewSP06jNyXmF79Vb/3\n4mj9VD1kMXtVu2BG1evdmvtFAGTC8SOBWDNfrPMz4B8kIpZv3FMW87jV8IHP\nA4fkWaHOZpkWTB4UyTDygTSIZcD3lCK7HEliy1MENfbftU0TqUK1NXt7h9xP\nMwQipXuuBuUZKT844lyEvf7p0iLtnVk2MzhGyFxgN88dVKYJZN9cYHc1wAPw\nRYH8H7BVWa9uN76yxcUtMY/YdOLJM5DnxEODFY3+t5Ei3q3J1Vmyh5yjMO6z\nTfmz\r\n=qiKl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHNVA9qHg/doM+o+rMXHJ24BJaoa1AVP1vz3ynGNdOCLAiBEEGcfKSnIghAyqMZ40sjzLRKr3+ZKqks+PJa18zETgQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-ebfed6c_1620244727252_0.4999241422786944"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"1.0.0-alpha.16":{"name":"@alphaflow/resource","version":"1.0.0-alpha.16","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.16","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-TDm+XNmJvFtzlSXzncbmYSvQNm7PUgHDYe2E3ch6bumjMsnx1MGerxcOC6N7px2/NyHrsoRKkbE11FDlP0UtNQ==","shasum":"38f5e2bca19742519fc4dcfb579b82788bc76598","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.16.tgz","fileCount":6,"unpackedSize":445314,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgkvwhCRA9TVsSAnZWagAA4GkQAIbCPsuEd4pDITtIk4j0\nVtxlrQhqetOtYAOJgA0zFTBJ/w6JofJNyPqGLv4aKmAJd/JZCYyxvzWusiom\nMS/EOTL7aVHoG4ZqVG86EegDFgebEWVOx+pKoR1duJznQkpxTXwzkJ5pqWCM\nz5kTpx32iEbb0Ximkq4aDGHcUueh+dFZBXJyKoMg3QVJygaJg/GFCJrVpQ+c\nEf0KhbZOQY+ScQYolnTh71k9eXDiFFgKMNH/G2u07hDMRIKZGzsLuDarhn6z\nAr2d4Tzunlt+MtQzOmUVbQaEWRETDtxR8bq+EIGMoCtMtZ6tXzxb4MgiIO6p\nzeEQ5uSq6ecvp/KwmRghaRaKLcx2mjaY9SQTVNiqfnNtiNnLYax0jOQx3uLo\n9D8v4fmhU90yQuka2HK4sPima+QEYtyNgt6zSq8bpqQ0XyaCn9RYBu9HnISS\nPgFGJR71thBktMVg+peyBIYcoxHxGZJR/9ya/j2an0ByzGGcC2FZADhF03qL\nxpnB0BPA+RDVgWfcSYTRlXXVBN75MSTEx5+jbdUE2vNgU511WOezzLTfxpTh\nT/OZdKkn6th69Gr1C2BrWVTT67rf1TACS8Wf90iYXA1+vqF3FHLqSAxZnYnL\nTmqcJy4LTeV48nQrkcHd3SqmIOZYHJ73J9krlIRQJ+FTn9Tz/MlJmDKx7RXw\nEbIj\r\n=RNhl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDYGQdiXccwg7LSzSPQ3CSDv4ZbFSZgl7IJmzPagJRwBAiEA/zE6PKnAT13qBiYiOmREWj0XpyGgPsCIKffyWQ9ohyk="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.16_1620245536825_0.5612418995341029"},"_hasShrinkwrap":false},"0.0.0-preview-5b61af9":{"name":"@alphaflow/resource","version":"0.0.0-preview-5b61af9","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-5b61af9","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-j3ezcAIZYl8JLTh/ECjlKHrNsAvJnewypR1UMo83GmTwo6N5YRDMBej7LLg3QYv1dq7GmkEVxdLkMIAglbXJpg==","shasum":"2d7c1dfaf995661cf2edbdbf59f209328b41119c","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-5b61af9.tgz","fileCount":5,"unpackedSize":443263,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmuJiCRA9TVsSAnZWagAAsFgP/i5lLdEmKPlvfm/guxGg\nNvnDS03RH4S+mrM364ga13xCUEf4HPWwH4XrX32c+M5aysWN9hFHrbwYZIRk\nv5qbwaD+B0iqkOZx0Rd/FSqMnQMpD7L806i6pzO1j5Kflq7LLyU/EadIl9Df\nHLtuPxfSulqv5RTzzXqY0PVFyhusQXhw3kSHiZ6rc9UpPpPQKlC7gj/I1bq0\nEbuWvzyaSYGZG3dF/hhAl7nSIs1L/hdDxSTi5HWsAi+TVuMo8rP2Jb7KMvfY\nYeEza7couXA5hM4lNeQ02koK+xWTq3XGAplk9Q3Cj6heZ416r/GKRhB7mh0V\n1mcSZ/Y1QKuQkMLizERbmGaeyU0X5kdgDi3DzVDlwTY1s2bTiF4TUypiTu1F\nP/QHhQOw8QMG/N7Dj00RcFkVLjLWvkO7XY+0J+P4pvgaFOhzlN6xYbVNnX25\nfQwMEyunSn6VRzAXPh4JsM1xThbP4iWCXVIsi3YEtOCAT7YM46i8AtYkI1zi\nyGUcxiYAjVplycq8D0348yoSHiuHgxK38UR6trgkdQbS0u2ZdjZxf0wmlSLu\nECkAS0XnNofcISsfr7aIzfEm/ABjAjg7jpso349jhTYFZeMXFszhUpm5/fQ6\ntYVQqtqlTTF9gWJSuNTMbYbsnmjH4uBrLxusZrfA+STEZuGcu3SK3RW9aNDd\nscMW\r\n=MIX6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIG3PgeWF4K9Idkr4xaPQNGXS9iPaGOFRz0t0MqNypfgaAiEAqvSZJXXQ4RL1i9HVoPh3s3BdBYMnjpYD/UTenm53zdM="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-5b61af9_1620763233843_0.32141727259275643"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-522e923":{"name":"@alphaflow/resource","version":"0.0.0-preview-522e923","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-522e923","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-g0EMSGWLPKXlbDuyQEkjk/mefH8MV9KIYlUEHc/wvPPg2Nz5BZj56TuqwWsMY9X3Fdulzq8/9OJd3kZNs1zlrQ==","shasum":"7647ff9595d5e544d122f15d65767868897a7e7b","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-522e923.tgz","fileCount":5,"unpackedSize":443490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmuVMCRA9TVsSAnZWagAARvEP/jase92OvnlhWOb6UNJF\nsO79f5k6BLJ9+51EuS697jmAVkpvC6pXo1TUXFEkV7+lt5g7nNd6SUg8ybTl\nwWUU6TT97STvdcRPBLBIHThv4gSDY9Lchs61mFSAo94JpOlCWb2haQGjp4C0\nYNGytZPu+T3eh8dNAR0Yq4mpOoeRYU3eorS+aExVxMD+/VGM/i1GMChwt5c5\nT+YHrVOd7cu3qYX/kuFusMxMJMLumidZvAvnrE2MFWWRx8C5tbvl5T1eh7me\n7USOCyDBVmLWPWEcPfVUiT0WGdIoJOpBmpVhhpQbU++eDYKacycMZCvNqRNG\nSRgrMxePLnOEukFfceK2r+JaaB0C3eoBsKzg+xuEmFzLZSIi5kCTo19TIY8G\nVdK5LPasl11H364sH0dpnLaMGpeiUTiXdd2Ayv2gUJKIC0Sd2rgB424PpnEE\n05sa9c4pW5jb/u+MQ7OqtFSYIeN0tBtddg0U4hy65IJQDemA3UDxGTd9tqBS\nk38dlmsRvQZy1kt5CxtAIH7qCbcQ3j+W2kbXx+D67O35wk8cPJarcIy+wARu\nPxLSV75/xuyAkrwEWSTOrqRICKDslld8e7RHMIXypgvTaCawq1hZcNRyBjVf\nPtLUNdn4wJtmYQWaL42m8YRNoHs0/DXS+Ymql+P8S9UEnixXuVlekgP+AAkZ\nctIw\r\n=0NI6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCRAYNbmQD6vUOF1m/WWlgzeDQLgUDHzHinyytHryASugIhAMT6uVmRWzMic+LS5Y6+wG9+TwePlhm7kVFdAtlMkMce"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-522e923_1620763980043_0.5528263704783873"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-d8e9c9d":{"name":"@alphaflow/resource","version":"0.0.0-preview-d8e9c9d","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-d8e9c9d","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-9+FXtK5ChuR8/O1iuf72ZV0yrcvUtufOskt//4zbqidB12MSZ/Lk+C3rHsHcuq3lpj3rICniuqyrFL/cYzU1uA==","shasum":"ac6cb86c984c5596b45e5ba134dabbdda5b2e3ad","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-d8e9c9d.tgz","fileCount":5,"unpackedSize":443596,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgmueJCRA9TVsSAnZWagAAYf0P/iqwr8FC4dz2A2sOcX91\nP2Vg0u2u82SuZqAZ65twsmkh1G4Wh0K8ymDGLp1pEwOjhcY1BOVQ102Xg4o0\n1uehpZd0hP3Zl9fSXWpcXOsiFGwp7rGkrrL9zvflLuq/yfIjve1ktkaLdC+w\nZmTWnY2Bevq5+LwfLu4RKSs86RjThpufhJdNBsw4wHa71pTLsUB8Ll3iwf56\nHodzaW3I72czFacQTNbtpBpqx18LNsYHyjX17+BaB5r94gAN3SmpGgprVA4Z\nCMYStYrOdqybGnJ5YCWwBNtaKH5MEEhsTjPrd4B48Lo5QCwMiUSj0Ceb2xbV\n0Qb/VP8zz+6Ha7HDv6+RgV5T2pEwlT/2NWVZJlFd7eu9qF3dP2m0dlIdqxBD\nKwFMkQkpgDJwnEDhKVQ5g080u1Q+0sHgZmr3ChwR/oUPmLRyb6Q0qZpOHQf9\ng0ph+M3bLre/4fE7NUeO0kXbUibOUdkeG1TLxZc/zFLO5lh9JOgqssl4ZcN2\nShamjp2LKyNSiFe/2CvzP1PVOxhsxR1pyUcHjHjAi96csDUe4Y0g3cP3qW3E\nEOeejQ10hGLRCKHSjX68inWi3bs7ixRVGbx/+JicL/S9qYlE/yiSBZdyVfOB\nrrrkViObFjKqiuG7UrwO7clqxhEu3szunjIA1CZuQ+vVEyd7qKBzGU9OldXw\nR3ZD\r\n=pxLj\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAP0DBjf0k1cILl5kbfhizmApJgV/qkib2vN2m6fuqkYAiAHUcRkQyeXPauKCfUSxOfqcODaQnZCjKYARVf5/sqbLg=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-d8e9c9d_1620764552767_0.3820725806763263"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-fc1d929":{"name":"@alphaflow/resource","version":"0.0.0-preview-fc1d929","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-fc1d929","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-O2on2xcxR8wlG5n+rpqWNj/UfokXtqky28HkvIEEsh44DpvmAJwtOrnozaZJLk5GmgCM38Sn5LwhHnHqT2+aWw==","shasum":"977700d08a12c7b384b30f1241dad5da933bf6db","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-fc1d929.tgz","fileCount":5,"unpackedSize":205490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm0ewCRA9TVsSAnZWagAAm/IP/29yr5TtbUBIr5BRPF6S\nC2kkS681W98f1jgTEzTm/TwqxdkN/fUQlKByO9XqMNgSUuKH7dI1DoGbwF/G\nRcGZ3oL7cao9QNy7ziDiWJdLrvAggK/sv1QZCWiJah7nPES9WAdk27ECLPcY\n7g7ZWGziVUMEUGzWy1RNCIDWEVly7nTFpj5rS5pVaKw4dukkv2uRUldrfPgY\nqBCRcZRa6y8mTw05FWn19gjJwWAF2Y96KydEUYFrZVsAHkeXVCij4Slu6qWw\nR0MBES1q4sIjSVI4AkIKjMlaEYSy0O22/LVT68WNBf0LffysvEj7blfiKqhq\nydno1KBBvLv116kWR8RZANth0s4O8K4+zq0zN682rn9HH9V2HkglHxRJUpCC\nIYcSWOzAkJjbJvIvzv5ChZ5l64UicvGQPdcZIHfgUWLlmNOHOvb69BSTbS5U\nzw2jPGjI0+Id5y8VJNN1ZeMZs4Smhj+BfvGF99fAWCJwyngruiZ+rcoCsBJo\nS7EPhPL8YDQqW6eu/dXJzsmzQ9YoWyfxXeOMpXXVwmdH262BMP7CH0Pg8Roe\nyToqMp8uHxfXD80iXoMy/bX6mIpodvQbrgRJR/Gq3uZmHJS9muI/pTyd0NGy\nB0330noAOzR5cHK2UqAfsxFupfnJWqoJdbZHiFAitpRL78NqXN+mzlcoL5Uv\nJvUo\r\n=eIX4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCjsYbdlTFpbxoArAh+imVbJd1L2uPvkd4gIoclOPeDwwIgSmoNuN3GdWKY9wpUqLfd14z5j44cuZLMik8f1sn5fD0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-fc1d929_1620789167775_0.844103417321673"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-e4810b2":{"name":"@alphaflow/resource","version":"0.0.0-preview-e4810b2","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-e4810b2","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-TtTokwqoZFnYKPaOdXoCgvMWT22TLJwGDSBANwBA3TNAs+7kWKRn8h1aYTklTm/jVQ1ywzUsabFktD59leU07Q==","shasum":"697eb3563045d396b95493e2382184b221f51f8d","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-e4810b2.tgz","fileCount":5,"unpackedSize":205490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm1iICRA9TVsSAnZWagAA7RwP/3IFKUb4mAUrtxIVXEQm\n20Qa6o2eHm4PuIOZNmiiNEgW+MFHBZspPGx0OuARws++3q5jVwNX7t5x8yrx\nQtjna+KHY2UhfSPncIJJngZ9bnfvLL3o0fHfX0fyrn6QTc/Y2iDqS9Q6CBEY\nsuCnsrMnTwte9KNDSMlVwhcXnccgQk/f8s/cGFDqa4H69nAjCBQANpo7/CGN\nB5KMK9s2TFrks1G7DrktAVtBZWrKdcaT1aWtrdGecjXfREdGnH1jhLv6BO3L\ntlZdLj09AxhL2Uk9ZPqq3+Rr8f+U5osDz5ruP14osPH5LY92dPpqg7wrIb8H\nNMFUoy1iNGh6x5z8WsNi1jzTnDoWLgwrknXdGnBKwL56L5kSLLipWLY9i1eL\nm2EuuhWmlg11ziKEn0C9GBqrAteC30dhV6ZOfgsp9/VulDybai1ynraXQ0Pa\nWjmZOADVCB/lJl6pHSqSyYKzGMsEVQbRZEQeaPcu2WY6xnMYx2rzTec/q7KD\n2xTQkT866IZrYfnYHpONnkbhc8LXjjC0QduTB2BSEH8zmQTtcnqZ2kqmYvJC\nIDjZQG906Oj/xypjvw+IjW86cRH4b6qejOonIoB9OzuuVIGv557mr15AeA0D\niOCWxfQ5iqrwc5GYOJisDfP/lyy2Rgxt+mEc5W9Bl98BQB6ZPZudzThH9dOv\nfMBE\r\n=Peqd\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHXlq9jCkpk/NyNGegFL1VqFFSIDzaKQunZpXVicUu1hAiEAtw/Fe5dzmRMilFIexW10H0uGpIH2z7zIT5lUqrwZvFQ="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-e4810b2_1620793479655_0.747529662684276"},"_hasShrinkwrap":false},"0.0.0-preview-b81878d":{"name":"@alphaflow/resource","version":"0.0.0-preview-b81878d","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-b81878d","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-58QfwaYhHqH0dcCYmXt8OLBpd0rT3GNGDDtYWl9eCCx/5vSXyn9rQbZYpjRCPGKVWKC//UNGH5NMqUpzMkYgwA==","shasum":"9b2c246e139992448ba4ba6c31bc4768e1ba04cf","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-b81878d.tgz","fileCount":5,"unpackedSize":205490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgm//ACRA9TVsSAnZWagAAJNsP/2MVwAuPdZE5s/h8sXm5\nzwIPQYk0B4jsY9KkGApvp9Ofh/OncXBwJ10csN3j4UWCYW8MFBih20LSzEYh\n1/BT1KautitWXTxzRhxk4XMP9MAoMaj6c7sJKYNEfSHuPFzOBxRLiiLUs1Tt\nkFfIL/IYzf8Ybv1eKeJGmghSKYxXvJxb5OmTAiJmlvegJzsI1HxFkrgcJdXx\nQLod5nLfx9QAoZ89fB1B6codOzPwthllNW7QzdzStFab3cYLvP9Erw/3dl35\nB04PJ/ZJRzsB9+w1VXcoEyNsidBFUBxkdjx0ikHXZN2KbUi+XjBkGzdcnO3+\n0kBRiViFvtJZ8iDIbzQg2yUzo9YqetfePvgKf5IUpxgbIIxqratAlsUQuWIX\nvq6Gg3OLY4itRYdoautgCkfEjBw9FtH5oJYL/y4AHaOu43wqWUMqjSrCVGCv\nIIqwVyIt4wq+5lB5py/Kc9TnOt+683RYia7e18KjtAHFy0PSBz45kTlYSlqq\nsCxVuBgs3Tc8pDm8SUQmF5bfoJunZ3dEfgwnquFWNSMfkJGg00dZsPlUaGcD\nDJyD1Bqdj/y358kfNtSEhBmkMOzorWAFYqLthZMe8g3far72HokixJo1CK9F\nvRw93a/Jo9ofK+qCmyN1pz3F5t+MWI/ozt/JPKyGIdMeiCYWvMVx9UEf12Ec\noL/o\r\n=e74B\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBBcIgsJ1BoFjIZFB5EQAFFKcYtR0WQsF6tfdusowcDNAiEAmZf1wzUKqn5Knz1kVhhTBv5Jud4K97iWBmNuf76On5w="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-b81878d_1620836288232_0.8714723747741626"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"1.0.0-alpha.17":{"name":"@alphaflow/resource","version":"1.0.0-alpha.17","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.17","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-XXv1ufNm8A1ykK26Hf4KbTVGhpLFpnOF2nCBtM6g0pHZVgPxU2cK+YcMDZ5jWlQuYS9Es5+7wccqq+S7Qpr0Tw==","shasum":"fda77ea3eeb668a6051e7c69e6752d5ebfd4a6b7","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.17.tgz","fileCount":6,"unpackedSize":208155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnCTsCRA9TVsSAnZWagAAUsQP/0/pUFyF3RQUQkShhQJI\nwoB3P0rTigcO9MPeEPNQmoEsHAvyU+0Q06s1b0UMh4G7+2+fFTaAbldky9C/\nrJZ60cB14HIGTSC4VLXOSZzmgrXrp7N7P1DWZ0TT/RymWk5HfgxPkAuD5fu3\nYsdaW4W0g0YSAeVepoz2v3d0OVchjJwYXd1p9D4mh8B8xmoiQpXfwXDnylaa\niBN2tewuurydOY5wNw7lq2wfc562dlc9KVoTddPfrUj6YkdQWai30x7OFiWZ\nhEDJXCGujihV/nwxz3fy0DaRYowA/fFnXaSvSXtfdqHDr2Jbb3BZT1XXttX7\nOOZnKxAwSx2BXWyV7ZOqcee8pISCq6GWa8BPadhSUpap/Gj7yKOora6vGrCF\nCh0B2EV6MgTEScU9Fsm7Of28TQAnrTQCF/QOEyHx5mZBMY965eLLicDg00aT\n9M2ixMviQzoqFcUnD+2tIBVnItDPLqsXOUbuoZR/o3UWk7OvHH3CRmfy+FSr\nl58Qd9fzbxIK+8nkqcnx0/kaAANT+g5cGiBtg1pwUgOEVQAFtE8GZPn8XAnr\nwDV62LN/CW4sthx3lgkGJYASYnRlfPXXAKfVaFYVqUROLm0LjVFabMLgdCLX\nTQDFQiTQkQjK8TV0IOMiOi7UBUKQltjzozTrgopE+xype2z/9c0ZfEwlV8+r\ncONf\r\n=vPov\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDhcYWhHR/MxSiTpI+yCapRSrekOE0G7KHkEw+YTU6p2AiEAxxxJsLqxZLr2Ad9RxrOEDdiremqOHIXnS3rRoykTZsQ="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.17_1620845803255_0.9812169147966561"},"_hasShrinkwrap":false},"0.0.0-preview-d477d4f":{"name":"@alphaflow/resource","version":"0.0.0-preview-d477d4f","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-d477d4f","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-smN+1TCYPtjU2aN5oK0nG2oToIT3/0CsYxg0B+sn+FDgYHmXtTH1SmlMMpM/ah7T0cAf8RtpIOR+SzmvLlAQDg==","shasum":"ec15bdb79947baebe81a224d9990352da426b678","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-d477d4f.tgz","fileCount":5,"unpackedSize":205490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgnuxBCRA9TVsSAnZWagAAOHIP/1v/hVlZSqQN/Ot+1RHu\niS64PMaKeesKAZzcuse0ogMs2++u+SWiNDObH4QTFlNKfTfW/BSj0Y56KJAi\nkD3RGEI+GJEiBRHkgChHTy/J5Qobo1ZB7gOrhPr75ptiYLsJFMZiUUCZfQNI\nYeXxiqYXxUNFjhmhABn9h9jjXKgPNDq12SMp+MHT7jGRraHWDf2136fr7O1j\npBh4/COK/HsozwYuhtB+gkuaTiaJXcrdtpHlhxcqKH/Vgch/Ta9bj1L6PmF3\nddX+9onuwQw4a2k1uyLpWtjBZQBA0W0fARrXCzd0SYykGbOMgBVCd5JLQbv/\naBKksne3iM3ooiNX2NqU22Rdh93IQ1JuwR1Do/0lFsvf5RCSAZ4H1d/UOyGM\nNmI9hNAm7MitD3awBLp3xswDSia5gJW11ijh2qRk0zIIDSz+YP3iRQOmg3LU\nrGsJbNk5cni3ihFYCFJcz9PczXNg7IzspptpsC1kDdNK6Q6VDtjSzgOs8ATA\nTdsQUnDC8N4PYLIwguV1zuhNzORazzfI3LWMVabHHwxMcUUUwIrw3wZwb0I0\nsX9HW00daJc2S5+2nv/Ao/007JuwWqfwtXPg3XIlg/DJLPNbVHQnd7Q0Gud7\nufFziOUu6sl9giGw5t6sc/yGMrIdT1xrZwT7ucfOlGH3oOdAeilf7F8z+jNP\nKjm/\r\n=80eV\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID3OXI4Ycg3hOJDINChXahXjRfiPcELRduK6tOoWIXrFAiBo4xfXg6lSfrukqOP6Wv4Ncpg/1LbCWWz3ZC/SQPvK3g=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-d477d4f_1621027904790_0.8082251699164626"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-6838b0d":{"name":"@alphaflow/resource","version":"0.0.0-preview-6838b0d","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6838b0d","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-sTaT6LmibqrdxFQdtdS85yxte2TTodhPpsZ5WByKWCI+743NxtVNiI1/Vk3lONyeG4xdm9i81u2nWiJ6e/ppiQ==","shasum":"bf4cb5f74a327d02c3ce5484c6024c85199bb3cc","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6838b0d.tgz","fileCount":5,"unpackedSize":205490,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgorQbCRA9TVsSAnZWagAA3fsP/Rnhz8R1N63nwWws7rVV\nC5w2UU8cijc7doB6L2iPbvx5kaTZF1Eaq5Iz3rijwOHxi0sS/x/iv7ZOlvfq\nIN0X0q4y8zrx8lprNF+85FGq1fIuO6x/jCcDMv7p0QdLdfmm3nCPdmve9myi\n1YP0/PKmx+TLjGg/1ll69xo4pIyrZa/IdaWKgvGoK5wRSC2tiPwfUQYLgaUV\nh14ZNS+95JuBYdvMr6/dfPswLlLt68kYbP8yV2xsdB2k30f8lVX1z5DlaI89\nVZyZktKS+kk7UXf2HLY8/lrufE8X8M5jT0Vi4GHqr4GCcBN+ktPqwnPzG2a5\nFneUkC4iiJl5btXrpf/ctoEs290SDxFT+hQskY+V/iik0U8FJNL5uN0n57qF\nS645KFLWVBCqIqtMFddYoTiS9bo1xKoGy4EijkpAQ0scb5NxHq4lmOYYVG9r\naUZuUZYhbRaByFd94TLW25tUV4X+3BkomOVtYx5WzQttz8PX5Og4I0ldZ7c9\n7hbijE8pzbBxSMEplBOZ3ijgS+MI8l8+OJtyPL3ZXm4ipPvH8Hr/Jvt0gNzJ\n5MZ9XfSCwxN9Ua8v1gBs1UTr3bdcUB4GOBqpOMUZ9fGXrdtbQaKqpHxCJdaJ\n8AK5JAJHsBbLpxgzpfjmHlOXP7nZUJ7JUWTSI7QOZTnpf+MDxqifNhy92gi1\n7cgH\r\n=okk2\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDAfgolpLhYNO+/P/n9PMvfPrxZmNOPilRGafTQSYZ6YgIgK8/OKg99PQVO6mumbW2stBKDHvN9agnilsJuWSRspig="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6838b0d_1621275674661_0.004008835830857427"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-fe51732":{"name":"@alphaflow/resource","version":"0.0.0-preview-fe51732","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-fe51732","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-uwAcQw4d5ZE8Ij686KXS49be65nRlJ2W6oqKh4gR+UeMUD9gTvmwf9JeEtYGEAa/7NFEcBOrYOueV447fzF0kA==","shasum":"f9ef84117925c57c0fc862694deddbc913801705","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-fe51732.tgz","fileCount":5,"unpackedSize":205538,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgx6w9CRA9TVsSAnZWagAAg2AP/12nef+RqSFrSx+7X8rD\naJnEWnRX3KwdUHy01pH+Xo6dTya7SsgJFwic18IspPnhm79adly3c3cIIepJ\nAJQ0+RvHioAgb5EBqzLnrejgPOuYU3SzC6sF7vFPE7yMvAV15TvoiKcCrJEK\nozhJaM1BEERLu8dFgu8HiyIDiFagvZ6srptyVv1oreSlWIZDB0x6o6Vw/P8n\nCKrud+fbrpMYR8sCOgmpBfLOoWpBmMWD+fgM04w7ay9TTmcAg5CESMhuAR6d\n2MVvyvEdGQjmZ3uMCy4uRurVzlblAPEiyGIupRa6PfaG4b6C/pLZMR5g3guY\n1g9sH7V+R3QEOSD/ncKJ61172oXYmtGLPiNJ4Gp/bnGsw1vcl+c1lquxORuT\nbwRsgwaybPu4R9s67JTVID76QCrt1PPuza8F6bkKY/E2IxWVPS6lzyK53OAc\nxzqWv29u3hARDelx7FkfzsYOobDvyM3gaZTizSl1AOsszdxg9ft9BUq/80Qd\namUvxYIF2YLH2tSpu1oXaQMC07ZH2g++InmB/3TaGrYumbp090lHdxj56Ubz\ngDYEb0PeRIFyx6Ox4vNYBhz7NwyKIh29NWF2wRbrRIFFNpLLCo78HzmHiN6R\nNnPlg19ClEfE39NkUtcN4GONsGMUXGqiZmS4c7ePUSogeG2my1Vh1dUFpnDd\nluHI\r\n=Btxu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDQXzppNwYGGyAuBG8PxsS7DsFmh3LnD+8tLBmvCx4BZAiApErwu0T+ZiGWMACWJb5v8dWAB2B7zcgDlt8YzXtq0pQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-fe51732_1623698493571_0.07353014497675625"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-4de4a9a":{"name":"@alphaflow/resource","version":"0.0.0-preview-4de4a9a","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-4de4a9a","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-p4YS7uaM17Gu3KlP/SWVS9iKC3x9jJKq4+nEDcCjEqmTke4aPBMjY0r0MzA7H2AR9g2sKKhBcT62sRsZHQ5CbQ==","shasum":"4d2c7beb4e7141d7dfeea7a950fda82cab0dc808","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-4de4a9a.tgz","fileCount":5,"unpackedSize":205538,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg07sLCRA9TVsSAnZWagAA0F4P/1aIGuH1g3zm9/rqxGAP\nY9WVl6ERg62ZSt2q7DlXRuzcMFkjgrecTPlBPokH84iLq4jQpRSAaF0czPN2\nRQ7pZti50x2eCIL0zHQEeUITvSZ2T39FsxJbhvxyLBVDdh5r2nxCK11tiZ4d\nclxWjPz+01qaSKHu7naV5k0gRRkCp6ccwOcUDe+M2On10tnDNWoEM1IFJpDO\ncw44f8uUrZD3lv+1L052xxdj/9s5Dhr+Ylxn3dKoBhMMbLQyasmS8mAVvwpe\nhslLDaLUoAeWQi60gY4PtB1Z2l9Di4lxN0KVTr2a+JEne8MRPWvTnXR9c/BX\nfvKQGc8n+UZ0+Z5CaRXSI+zpHsxEB6+v01afYjq3EkUtqbHit5TuF0c5Yno5\nEtTPL1TSBoHV7A/1ePo1qO9TdQT+3UneAYv3Gs1TF7IBdZ7L39RhrwsrGMX5\ndge4HZbx50U7WNY0Z1NJfGgKo0vTl5EXgmar6C4T596wYeW212mTPXd6cdzy\nmn8QFfV4LOUPC+dJhvdFfpWkS46H758IqZpToVejHyRHNpagx7B9oU19pPSE\ntEM1YVjrBNBmKNe/XeqXMz9Q6MOOZ22i32ZAuV1A1wGuNtfmZz6Rrp0i0uTP\nMdYbYzOKUrkcdtQvLgt+qB2+XlT09TIfsMbbVGoaHP1RvVaUIUETTen7IC5r\nqeVe\r\n=GWB8\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFofrDNQVM2nGw7YN/xthG8LV934Wg1O6SzDsJEccG3vAiEAuCT9Rb4bnP2vOHpcHfvSVNEYVCALZMGH4K6XIoLGsIQ="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-4de4a9a_1624488715223_0.6197491372357042"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-83938d3":{"name":"@alphaflow/resource","version":"0.0.0-preview-83938d3","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-83938d3","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-dlB9u/4wKsLs4FH7FDRgLTJ/yBtBfmEz1Jj9ODR28QBfBF/sTIp8wnkzzzsSBhtdEydCsZ2DsQxIl8kn/EW9nw==","shasum":"1ad48000cd053e3ba0ec0001bf4ced9186f0ee22","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-83938d3.tgz","fileCount":5,"unpackedSize":204601,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg322NCRA9TVsSAnZWagAApSoQAIE7815reVM96rHk5Kkf\nst71hg6EaAzGQqLYsVcf4q3BM0SZQks5U69X+odQyHSMhvJnNii1PEn8+zyz\nvEX1UICU1UsqTVzT6SVQ0jyKBBTr4r6gGUOemUD9LgVGzXoiTm6S6DJVnoU9\ntQd64NmY7WmXZ2UcAqUW1NZDDwslQXjXsl5u5u8D6RXFk2/rXBzuL5cjyzan\n/01L0bNQ30peIyrCYu/2PMDED0kLVIiqXXIAkUIkAi5a01eiQ6kpbksE5vaU\n6CE+uPBhvci7ggZ4WA02TxlTeGBzGgYiCMym3qq2fSrUfCN4IF2EN5GLdCs4\n1jrWrSi89oISBK02lUb2jSkrQJB4rHDRN0yUMWiuYz2cPfkBEgv/KlTJm5Xs\nZBOSglGtJ0rNVUSfQdZIPY9GQ7JKsZngMTtqcgMw/YL31j0WPu2/9fHCf1EM\nxHqURcjcIdI2LlfT9zZQ2Qz8rIqUz5MXsGS2ElLUzwDW1535QyvgPhRmyiAn\n2xfvn5NWwIc57j9f8DZuXQNRara2bkLNeYeEa3sx9M5DRQhpMkTkEQxAporQ\n4fXBuJZn23MU6b+Vl9IhOJg0QlQ3foBbZQNlqhft1DL2K6czTqksZYei3HbG\nQ+p7S1oEv4n2Y6N+pxM2P1BLulB8FpZrS0JtGBjtYQ6bb4NdZVK+ZhAhALyX\naffd\r\n=+5Nz\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDrp1F8rfVhFfD0HERLCEd4uB6DWvQl3ZIwajJxcLI5WgIhAPgNDBPjqVN3GjNsn8+IwSYikqp8iOliWioTFfyS0tzD"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-83938d3_1625255309189_0.5211866559640332"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-b922b57":{"name":"@alphaflow/resource","version":"0.0.0-preview-b922b57","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.4.5","@babel/plugin-proposal-class-properties":"7.4.4","@babel/plugin-proposal-optional-chaining":"7.11.0","@babel/plugin-transform-runtime":"7.4.4","@babel/preset-env":"7.4.5","@babel/preset-react":"7.0.0","@scriptless/tinyapp":"1.0.15","babel-plugin-annotate-pure-calls":"0.4.0","jest":"24.8.0","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-commonjs":"17.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.27","lodash":"4.17.19","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport React from 'react';\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-b922b57","_nodeVersion":"10.24.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-rn6n7+lKol2tpljOYtAiXZibkutaGNm3xUbisABdTv5n1SHp0ccvOvYYwryzRYZwn2RYeHYwd8V8JuNiJ72PxQ==","shasum":"93f64f122bd5b9623c7f83115fcb45ead43c096a","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-b922b57.tgz","fileCount":5,"unpackedSize":204601,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg324MCRA9TVsSAnZWagAAAjkQAKEEXbERCiWEyrIKsLG0\ngqUP4imDPv0PeHeEWJ4t5IgTIVFZ5WB0TDvjD1qi268Egw3OT4ypCwh8+nkJ\nyLErauT57Bf+RJMNppDB4thKm/C+RLOmfp8bzJ6g5nEorVMoCHJk/B6xpCrw\n1r/iL/CpJd+WaSm/rJoNMM206/ol1dTT2k4pZ1ITymXgnttm1eP404NV+qMS\nkhBsYu8WM1kMFlHq+6BSmayAMZP0MJsCH9jYWYk9Xuotbv0whkifWeugq+pB\nV440nOg8VxGvYr3cFDyP0DFNijtMESAL4GWFsBWOStXMpcM+Yt9xozjeL20T\nSqq/ZTqqRSxEXpAdQKz66gx00pYE246yr44UEFLIkoLO9fghey000D1r057K\nBaChT/rcb3IySN7zR7DrchPf9PxiT6QZrRPAWr7gV7uFGMQ+/SIqTe/+GgMD\nYwnlrayuZJRDtNkhQ9Cu9pFmNBx1WF6ztYCouU7Pf37qF4fIXOT65sAdT7/I\nHhxTks1vXjIzOYtVuQs+1g8FAlaqFwtldGoxfOs9/UvC510v2UkpT+Q7LQ2u\nD1R5UZcB3Rh4egmDKgX6MXsHMLSVZn2X2wkiDrBvfQM66Xk3xOz9WKCc7RgQ\nV8PPZQkob92u3JKqUgnI2b1VNvV9JaEVfShn8xpcwDPvkWEyynw9dH/ZHscM\nENHU\r\n=51Vu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAyrhyQKkYrbDtEzgXDksaQTvYZze8l4Ogy8B9A+j1rvAiEAsZVFAKA8fwhD/cvZv1nxnoh9TWUU/KBAheG/v/Egh9s="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-b922b57_1625255435696_0.6689018683923835"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-975e136":{"name":"@alphaflow/resource","version":"0.0.0-preview-975e136","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@scriptless/tinyapp":"1.0.16","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-975e136","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-ICtPv0YrYCEVygNcKED+O0lqK/GBlCfb85i+SdlzEURGY+bye6u5g0YGZJKEwR8qWhyt0CK/RR/YiQNJfF3exw==","shasum":"f16e1d7a6995f4b9ff843a2d44c89c956055b373","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-975e136.tgz","fileCount":5,"unpackedSize":119958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5JASCRA9TVsSAnZWagAAiKoQAJIrW6R1yjEQ3bNJYxlb\nlfRNOpTUSZGTDLCXabavFl6V/d1uk1e7C4ABLT/OPaD7pXei0B8A/MEymBrQ\nvPj9zeUgogCuRp9xjNK9YVzn2Qrm91FDeviaJR/8AlXUdjWm8ZwayJbv9Urb\nKSO2mmdmqL7rcgET0pQ+0kp45YRYhcnKE9qlSdGFDJTCvZVJkqphv/zuNnWQ\nz0mospSxCjYM+syTBLbL8rU7onZd2fFYQvyx+yRYuvKFPi+j88g1yc07NuOZ\n0oYjhkWBtDTQLoQeDCmSFNeqBJQZIam2SN1oOPo9mfZwT01SETnNba856PJ+\n5OWtJOzjhuHnjYVsqVuiCqXrmY52tS7YrC5vIyrXREJosM8I7aU9nHBQRdJv\nymZcoHhDYJIKnzPev2H6RV1pgn/VBVLz4uq1RAVvvuMfuK5Jziujx8lX23JH\nRgYhOzRVPOz9JJpQQw4Z2qc6NMyq5Bw/HvKbxkXMXcGG6hn8MP5I4BLMkGQl\nXNecCdYk13EKneO18GSLkfWtkGjFaHbVboLfl7CXMVgGdI9xVmDzyqliiKfm\nMGlFRD9diuqWMhHE5lbW5kdWM0aWG/1U7ht0LlCEk1kiTIt6be3AoXGn9rcz\nUDnId8lTBqsPLycuN34zTxyJJGAFmkLPdKS7QrVJRcioXmrAZocvGat5jgxw\nhmU8\r\n=zMyu\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDIt3BWLr2btdTrP+89vnCOe+cfV8N+4cON934DvQs1XwIhAImUCuQq4PeBuijo8RD4sHEOk8zhwMOkz58NvHKtORre"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-975e136_1625591825553_0.3484562658616408"},"_hasShrinkwrap":false},"0.0.0-preview-5e7fdcd":{"name":"@alphaflow/resource","version":"0.0.0-preview-5e7fdcd","description":"Resource management wrapper with a React hooks api.","main":"build/index.js","type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@scriptless/tinyapp":"1.0.16","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-eslint":"8.0.1"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-5e7fdcd","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-n61EwUX2DvA5DGo5mnFaqGqvQS7kIMLDgF8UKX9W610mQDgF7vY04fY8FGMiUv8Ol87Vrqe4Po8orx8Z4BWm4g==","shasum":"1edc21ec84cf83012a3b34500795f404332c7ead","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-5e7fdcd.tgz","fileCount":5,"unpackedSize":119958,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5NNACRA9TVsSAnZWagAArNsP/R1iHnhqE6a2yjGzjOR9\nlUjn3oBiQcnMaj42johRUcYC5nYnRFEW8NGcCAGoCMMwhYwLSCeSyLnCsHFt\naoM7GQETW5PJjejZgdIItqasemSjYynffy1u1bUt8vDQS2Cfi+w3tVhPaIP3\n6MBI3sk7WhoKacjueVYp966MN+8ZEDTiHg+R8NaIE6qunEKUkBEtd6A6No+T\nvafzkHBFKNLAORaDRZ6RM/lgS2CkUKbwhBiyUaXmdNhvrabbdvnx0ntmovdU\n3ncKy5PaIkHf+9b5+KEhE7d8fJNDc1VggpOYiGYAcXhD3eqTfluTWi6geqHU\n1MvkKPaVPT7Vj6v1Wp4uiUe7PbaK8nqx8DXvJjD3+ceRnDtRREUBmzuFPAKL\nh1n9F32/LawfSo783hBBM6oPrGNlluW4/URq+AASskXXfe28ZulunOyVpenJ\nzyBn47czKrnzEQk2hDX8ANdqHe37Gah1ukZ8NCNRMgkIPgQrXGMlkYmTharM\n1oJqHRjnq1kH5j4pnkXI0mHgyLkqwnRsBS7MnxjLHYc1nYEp359j4WSd6bOM\n2fe+m/IKa+LC5GJnu60kv47MEUPSA0TNQihw8z7IZ57QENZ9JAV4RtIYonVU\nZcMse+9TuWo2Q3NftFlk9od6Gazqb4Y14OcpQAp2cZQuLjm2PrpnYwgqxMnP\ngXL5\r\n=mKeY\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFa1yzK70x3g6SBAXz/mU1CJPEZEisN9OubTsTxVePn5AiEAnxUWGoGFmSEFWWeVRT2VDVCXyLMc2w7+wgp84HfNt/U="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-5e7fdcd_1625609023761_0.039464963056893065"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-e027c3e":{"name":"@alphaflow/resource","version":"0.0.0-preview-e027c3e","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-e027c3e","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-/wEsqdc0XQLDThTsXE35ol4mxXuV5K0puM24aurW0bio/zgkCFZA6ymylcYM1XG3hG2CGM2EzhZzWNA9iifnIQ==","shasum":"e746b9498c7ea6499614f7a5f7d7b898d45466e1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-e027c3e.tgz","fileCount":4,"unpackedSize":119812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5eMtCRA9TVsSAnZWagAAS+EP/i/tF6ZWr2k38wTQ3O/q\nMlYu+k1cV/0xydxTyVf6H/qv3EXBxVCALUPpnuWh95K7YBd9XZ3aOt02er3S\nraRAP1mh3bih3Q2y3FTOMndQVu4pBL/oDVdqziXtZYJkHc8fJvFw4U4kCEB0\n3JPoycBmm2tcsb51T1nzZ2Nr31/PAmS7JVrhCB6BxKsi2RnMkSBwHFoKzXYE\n8YRaK6WmYNjIs3Mx22Yx7A45waUtdIGvKSe70A/2//a/XXts458tERiXQLJp\nOKpUP0GOcl6L/fi0l5Pa7N1HeR6pMovgnwuQw2VqKzwwPl/7lyhcYGaEID7k\njihGEqBwLtG8r02v2lBjK49xsm2pA0zsxSRQyfb8rv7JLe1i+h3joHe8AZSZ\nrwYkJyW8him794i5B3dtco1o4+8g+ABRbC62UJOYQ7gagla1gSV5k0mmSGxk\nXgzMlgGzpKPD9BK+G5V5MivGDi+hkopl7vurCKbujNlYj5FE7jIKTgqQp8xx\n0uVWP6yx1qxdzs7zAe4mNOOe578CzvMJaE9E+WUUyA+/+OznCVAvTyKxMpch\nFFpo90mOhWRXOA2BvqgKkbQ/PWfdF3OCuWNnx+q0LK8gsQeW+ieDUbka+dnx\npuR8BDWxCxg6OcDwtCkutvMCNUeBvhHWGYaLIIiVFJjh/U89F7jna3XIG2IR\nuFjr\r\n=HgeQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIANGQRRWMQu2qM3ffTAjZOlwT1BIuMLDIbqo0xtkHBg3AiBP18m3ki5YFsA/kUwx+ETQ4IYrfBMzNDmbcs3UMVCUqA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-e027c3e_1625678634308_0.7337201587285156"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-9929f7f":{"name":"@alphaflow/resource","version":"0.0.0-preview-9929f7f","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-9929f7f","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Fm2CaF210WGvggT+rhvYYX8FO+DlMgsfOLu9idmGibgkqkcG6DlJNt1s9C/ZpIBwzJybhlTOJA3v7Uw9E59gtQ==","shasum":"fe20151176c59ed7a6b405ea337b9426a791a924","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-9929f7f.tgz","fileCount":4,"unpackedSize":119812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5eZICRA9TVsSAnZWagAAu5sP/0vYp2cfHqBeJjffngB6\nwiLmdvXHyOQadXhUpMNVBBT+npEXwPa5wmonevnk2OzjP/5FXmIU4M+C638K\n7IgQ4+8B3pc7xkd/Tfu6Q6XZ5EBbBKbEVhi/f0GU0klAEb+/ZKA9pqqg3oaH\nBp3RIU0L9E3tgotOSl/+12SVSm4ydz9jcNONC6sCA33cra+uiwdOZ1E4X6f3\np472ylGebwQTant3ETEOsOKWtrepVxrI1Qe4BuT1OXml6zxJHaV3z5d0xbBQ\nuTE6s3R9yyWdll2umAuNuVXK7STusMK09ipoqCYjy7GI6Htj9gAL52Xjq6oq\ng/p0kwg86PgFqlW2eoETroTQhK1VbvulIKplffDi/6yg347cGs3ZxHCQOE/T\nry4s+tDAWJP+f+i6OzuCZ+hbvR/gMIMcrjMyNinzjXlV00syBqd9CRcMWuyH\nJ+w8Rp2K2HWO9OhwPYFl2ma0SpVmF1SpwreAuzwQU+m93tU8Go/+zQbGrAWB\n0QSS1yI1Dtk/AbmV14hTfBJc+msCUsdT3u1VgKM4Ph36VqcDzAXPlO1YPvxj\nxu7eB7QZgphS6dXA0AqbFGFBAfP67uuXPT2PKv/vAZZq3+/7ViGdDFNGXYkz\nPWvF2qxV/9OFSmoYj6fZ3rH9E2cWfn6Umzs7hOzaaEnHymo1f7r0ur5D21hX\nYLNg\r\n=5M/I\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF84lHeJ9OPNieJ3EtvB8UG/lL7D+jCmQC+dgMz/jXrOAiEAy9l21pyYWYEDMyK+uu9SHhrGcxxnsGE4b+YbwTamY/U="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-9929f7f_1625679432184_0.9440041300032951"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-e692012":{"name":"@alphaflow/resource","version":"0.0.0-preview-e692012","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-e692012","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-BNhI9iuzD3s3461uyQNOb1639TrsBIx+w/RXeg19M8mEUHaQ4HWYlEPr5f3qYkebJNqdIdK3XWc10fKyrLywQw==","shasum":"592f1615461be557043f0fc6d3f3cfd91c2bbd62","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-e692012.tgz","fileCount":4,"unpackedSize":119812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5gTiCRA9TVsSAnZWagAA0SEP/RN3ilqlZiLSuGInuRrZ\nbEY6pzq1Zw2asCz6turFyzhu0jaQJXoclbmYZr+n3gP/t2VdlF9OTur+PzFp\nuylIAnTeVk9TUrBFRwNmuKc4IQwyscebQCLoniy8XNRSmR64NfyMd4HZcIQr\nL1yC8uUIaHK+Ced0Lk6AYXuD0SpEySmwUxTp2IrThr0a45/K88sVoQrRoQfL\nYw4D46s7J5LfmZAVjT8LbqLgBQE8gC2udBpkJYGhx6whZUsVr5xjjJSa4Ktd\nlk9G/7jqDaUyICg1u9o7hwHsYLYF0lYL4KRC26ueBfZBgyQkJahI+Olh/tHC\n3nJ9KBUEIXz5giDX08MWtHqe2G4laFnTqIjzkJ3DH+IyESsgiWMHNt2kNUXj\nqbsJJ+w1r0vQqkLrExXYzvAxiYLl4mC9tLUAaDXpWpMhDaj8bGywkx4Cu+iv\nNQMKlZ6clUNNkuBFpqFpflRI43cpZILlF8Rv6VeRAvVB+LbAqLVkdhS8bBPn\n9CFe613wxZbTY0dmsXxzG0k7dfIn1ftCRLrtQiMLDUdCdeZ/1SvSsKGCLpJa\nmx/zhEnyD1ujFK9HxREGQYBLTPGPNPhSuIEFB+ERqAJ5aXt6LmoeeSbqX795\n3WvIuMjGLW5kGHMmLx6Mj+FeBbiq4Qx83DgKxRGt5ydIHNlnOlOs197hrQLv\n7pzV\r\n=+GSb\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGjHwvFgOZhT722OaRJeZX68YsXlhWsxohfeUb85G774AiEA8D0hZaKMUA7++wOo4kT/GseBPhLMlvvvlIHttB4q8SI="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-e692012_1625687266405_0.7931385481304949"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-0f2bdcc":{"name":"@alphaflow/resource","version":"0.0.0-preview-0f2bdcc","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.35.1","rollup-plugin-babel":"4.3.2","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-0f2bdcc","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-uT3P4DGaRn8pPYHhg2ci+Eet8F9I5eLR/Q8DKvOVwRN2RrNbqW8RsirxwNaQv9OiuYEux4nZnl0ortseUgtSyA==","shasum":"eb34afb1e92c026df8d527147b813249b4b007c3","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-0f2bdcc.tgz","fileCount":4,"unpackedSize":119812,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5hcHCRA9TVsSAnZWagAAp2wP/jqM4yQ+Xi5+ycoaBj9E\nhm8uIGZbFjK1MMlID0YZJ98mS+EH8ti40Kr0IEX+3srhdDRN74WEggYbg92s\nLJUKcWSQciI04xZCjXco5SVL8xVz0ipoW1G8KrswIptSUpjlhydigddGVTfl\n6XeqGZUAHONOl9Q7WJNCttBlAoJvGTLesM5ZeJm+sa1FICWTg6UJ7+JZMDVT\njpSg+ad39rm0RoK9BLe4EpngqUOn4/y1yFrrE7VZ2VX0yheJN3iJmSSx6AC3\n+muK8HksYFlPAbEqTyZUBLv61wOR7d3mgCDUtg2xM0gBF431U9FPu8mgM9ZH\nXcTlJBL1LiTn9F4T2R2Rf8mg6JQrOOtAwyfXTyxjc7ZPlBUdqRsIuNGdeZdr\ncFbSbNYgYbD0GyYIHFJDnlrtLj/C//9Mb42SDJdKwPdxL87vFNffZcI9JkYN\n9Y7PFdmeowemBGXWw0+a+EaRC5dDnw1CF2S9rCVVNB/e2kbeHYKttumJWYFV\ndvx1sY63xzvq+ciwJNeRl2VytbhuC3y3Fi6RAYkWMzQUar2nz76Y0IXIN/ju\nt/AjslYr9vb9Eg+dRu40JDFyQdK+GKUgvLNRxNPDNyitGfsxniRrkJ0MZMff\nMJZ/tHgrbRa+wRK1I7yGq2uvbH94fI+eyDT+K1Rylhlm56oNh0yRR1eJxT5i\nip98\r\n=+9B5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCWAsEj3XTbfe1ZWKJuyCdquwGQUuRc0oomGkdR8eHP2AIhAL7VlxfIghEkZI/J/oZj8zYr4TrxgM06B1qIfiWNYBhP"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-0f2bdcc_1625691911078_0.6695638947899578"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-694d996":{"name":"@alphaflow/resource","version":"0.0.0-preview-694d996","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","@rollup/plugin-babel":"5.3.0","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-694d996","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Q7ihluPO1ze/1oO6w3YVtiniXBKA6/MAHRJWjOWdjy0mzwQAANp/yfRHW2dEJhVFnZpSEubAh1TC8Jv5qVvJbA==","shasum":"5f2712fd1ca7152d565117cf4dc85aa971974100","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-694d996.tgz","fileCount":4,"unpackedSize":119814,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5iaPCRA9TVsSAnZWagAAsGwP/R40N+/Jk505BI7VYh6V\nbfHQ5tWUGwwXoVjKM14cKSRi/qF4HrqtsLJR/oTdB4C91jfU2p1DLDktw3Ov\nQ3Fxn296IfWO1t0BW3c+UWcY1G3he95gRLY0g03xfOPx32gSRfSxXla1FCJq\ncwu8YIq6ERSyXavjc7prF8iSzsblEDb1i+nk/Ixc5m8EgCjJ0Go4wSNL/nP9\nfiZ7GouTOK2lYGd/g05moa2ZnkxF/WceiZhMVF17E+glR1ozT//o5adWcSt1\nirhfXIw7SZiVVXv+1xfZre+fi0zYZLkMB+f1HQUxGvDgqicwdXQIjAZI6zeT\nM0jaUGBrU+rsu5YILfWahm6GoVCVqeF4pBBRqvC/yiycr1ldk0GBoO1xmhNY\n/eKlO7Vp2xGdfRg9BzBRXSQZWaEzBkTMdQnK0puXFaRWP12nd8B/omtQQwjY\nC7gnzQqzYxf/uErw7OaThPtohB8RXTng23M5QjEAauLqcnlXi/QbMWlS+Sm4\nclyhGCwg9DNVv7aRhkndbAA3W9yFAr3WSICRV7w3pfdehGlg9ZucwcNP7ye7\nhsc/enf3YmjFTXcteCAgFAATIyXCMTiSFr+XCj2hstrMUBgrSUjeXkxte5AP\nderIdBQuGbe9aXSm2rYTFJYD3kZRdWeI57xBsNdVDfSlbc8Dza7T5vP3CakM\nfL+v\r\n=RGk4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGEA0EBwNSbCvkDWjKXydNLsK43KwpcGTFR+k1VNj5CEAiEAlR/tIMYq+e+52+MMapb8H3PwE6TiaAQ0SQc8CByuH/A="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-694d996_1625695887116_0.5282960840304476"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"1.0.0-alpha.18":{"name":"@alphaflow/resource","version":"1.0.0-alpha.18","description":"Resource management wrapper with a React hooks api.","main":"./build/index.production.js","exports":{"development":"./build/index.development.js","default":"./build/index.production.js"},"type":"module","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"homepage":"https://github.com/AlphaFlow/client-core#readme","peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"devDependencies":{"@babel/core":"7.14.6","@babel/preset-env":"7.14.7","@babel/preset-react":"7.14.5","@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","@rollup/plugin-babel":"5.3.0","rollup-plugin-terser":"5.0.0"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"_id":"@alphaflow/resource@1.0.0-alpha.18","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-+OQ3qBBs7G6cCMr9EgK+7fJE7qQt4BmsQgjEUmSJXabKDjZ3Ygv2Orvwpu8gnIQgXo113NBJF+v3Ga1zhCXNSw==","shasum":"d039e9e2cb01473c61cf847a75e423187805b8b4","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.18.tgz","fileCount":5,"unpackedSize":122467,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg5jRcCRA9TVsSAnZWagAAxIwP/RJGjOWfCwQqf2rjgDws\nw7ZWeBouTf1bDHOheQCQ0TL+hf+fXblTT1byhJOwj86//7v/g4PNTU1n1i2m\nZt6may0AVhvzYMAYvDFIyDxW9t4s2Z6dVeLkKd0jr9JE2qskihs6ygabc6RQ\nbbFNU8O/VvB+vDKT5c637tRGZGVrp8aPabLsuMdL2XUPsqTHMthBQS2LiOl9\nogY0VUaHoFtSsX3OCgtJ5rkBZ0Ega1qKQYQIB8yeRPkA/4eLb3MhJzUDE3MU\nH/Nak89/t1sAb2npTSKsjJYAcfoDmKRrOjlAccbrSXpCYR2o9PoSw3t9Ov7g\n5EAvbt46C+Hcdh/vd9KXvTdPDR7F8pbJ4zK9LMtwVLd2miTR9cCZlji95kQa\nCjGhlmElA1Atn87QlVXdgmy78YqzJXmUbIT2awMpS94iHxWvfm4KoZaj91Zx\n1HYToAgT1Weh0SbF3TTKokvwNVpTI+VTv8iOutd2jb8nbudp16HmklXw0Gxm\n6iqPge49nB+iwu7QP+0W6mUX4RTl8zd4gzFArVSHSUmoWCijFeDMtJX4Lm2g\ntGHF8nDzCqIJO0wHGNlnTDO+on3qrT5nVI0e/EXib358VD8YVJgjwxiaFt0q\n9e9ImcbVjHSWrcrZcGbam964DytDs6AEG7cLcLOfOkoXdJekdsVKY05XBrLV\nhutf\r\n=y836\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFKZcFyVEBXQH0usBXiBrSEqGFMwdGK1d80Kps3TsEZ7AiEAl54Y4Ti5ve/jN6mRQGDknhVSb7mz+YZoNZiFGqb+icE="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.18_1625699419478_0.12485865028012144"},"_hasShrinkwrap":false},"0.0.0-preview-d9f8579":{"name":"@alphaflow/resource","version":"0.0.0-preview-d9f8579","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.28","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-d9f8579","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-YoLM30YiYwItLp1h9aGMCudvffr6dbaB38pq9c8Nrg5JbnTjo3cqhyJyjV7WtZbW/FZufLas09CiLQZfoB7skg==","shasum":"080523ded09263850c0f69dc362e3e9d690f0e7f","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-d9f8579.tgz","fileCount":6,"unpackedSize":217703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7LrbCRA9TVsSAnZWagAAmPYP/RHra9YN6dWv51cbQAU1\nhbObaLLkfNCcT1fj8/YfVq790ffcR9Wuz/xHTaFxXzg2tRcWn6zzFxGDVsx4\nzW/vh99vEGhd5J4tmhfVfJRaDXDBkaq64Tz1gP6RS5x8DW65QdSTG23b1HTZ\nkGQgvoGEjzdFSkcIsTZEgJADSynY5Mtb2Grd/VbPI/qAjU2JUDRFmctcPTU5\nTZFFXHkDrr9HJiabNSTbDHqv6W0+kdAB+uRJoSCHv6pBE5dhmZWzktGFjS4C\nz64cFyxN0RTt1U8QSnGAAyK8WuWuRFTNJ9UkGUNB20ml9ogIWLiH0wsl8T2Y\nyGd2z5E0B4NCd1LK+rMv7wB12cILxMmGjXUI0FV5B0auyBoKvOahx/J87zM6\nCiLu7g9y7iKoETDUDyOe+YYM+jv74/dH/wRHcLk3KRx/cQGAOS9rZnXG62Fe\nG1nloBpUaai9mf4PbnNmA0K1/NhAPjV0JoyueD6hAamFiixNZw4duAm+OJt8\nWCJa+rB90pOqCMUb+7kD9sRPCpVLlrflTJikPcvQkXI40yqknIYMZnXWwG+k\nxmMSjoi+ba1mIT6tspdTM7bWddVshPyHguDhD8RNSa2CNZkrwSHPrXNHfejB\nSCCYDuttNK9Ci5a/3BLAXGQiPX5RfRemJNFcnpOllmY5+9uosHcKa6YFZoRx\nF7He\r\n=n7IZ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC6tz9MUbtxkywXCXNWFVLX5Z55O464xcJlMN3XiTzgOgIgQv/FqeU3dXt7fUIt6ximAIXZ+3n9uTlVbbXDYr/MyM0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-d9f8579_1626127066617_0.8129306518987778"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-2f100ff":{"name":"@alphaflow/resource","version":"0.0.0-preview-2f100ff","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-2f100ff","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-B/PgwxzbJnojOJ9clQ9WGmpaTzeZsxYeMM5rodj40TUYY10zvxMAzyPCSirFZdoA+KEw3rOQtuHttqJ33loBDA==","shasum":"47351404236bda9af63e3ecc21bef458481b2bd0","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-2f100ff.tgz","fileCount":6,"unpackedSize":217703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7L3NCRA9TVsSAnZWagAA32YQAJZ6Ff+Ny8FF6P+8Tj4/\noPs+MhM/5S8xbfrTSv8U8VCQVjr3BrIxNjcex+LmJoJa0gxumIHgSBUKVpDI\noD+ZwEpCzjKDC7SRdf2ABNBxjkCFW1IJzXgBwt7tVXX2dFJmzQ8GIk9OLKhF\n7YX6US94g48iORMCN7zien0TrKD14zoVNnWLloQA2q0xLuvzlGLOAuc5bHRa\nBF5+AoJh43gzI4w/aP+Xf8dmeWOF5EQJjPlYjmQ5x+xyV38M7+N6/SeZO3oO\nG5+B25VA0KwHYA1eHmhoQCf+cVgGZjQ5tbdZQiKtS29ZAe6BYkrjNaxwRCtg\nizU1DJ9Nzou33m8ZHqox0U8c3qupU0RvlD79Ov6OPUEsWGnI3OoSe617faox\nMF/kMjz3ajxfekfnK3B6xie36WLcVLCHev3LH5hRJU8nzlPRiXirqL4Y80uv\nT4GMDaWX9lS05XUOeJ3PiiY4WuTzdLvMkf0fTpMxEmpbhRynzmqQglcyhTAW\ntB6JOXeUvi7XIEFsmqBQcCdSxkR/Y8MoE6F2+ejiKD+zXNFQZL8RT46gJZNo\n5Zymxxqm3oCzIBjkvRdyzm0ifu9bHrVXv+zExzIg/5ewaVfUASEi2Or+po6i\nbqDQW20mV+eObMJ9qMsnappuhkVmv2WsBTSk9cliyjpdnwJszKb/He4ccZAS\neJOb\r\n=+OR2\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDXyiAEVTyJWBsDigehOQfNUUsSGq8PdtwM7YfQCouZhQIgZZshpgdtqj0qqWw3kiwVZ93BuZ4vHqg0KGF7Qzv3p5Y="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-2f100ff_1626127821256_0.3483009500780656"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-216a55a":{"name":"@alphaflow/resource","version":"0.0.0-preview-216a55a","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-216a55a","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-TDhGHxHdmAdP2GgOtmhHyDjFxC7henXotogxPJiSaQ9B+i3HPWXQs88HZJ8nBiJCDb2+ePY63MoN7/rkbkNvAA==","shasum":"9c4fcc526e8cdeeca3a5c5792840a3486e7238c4","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-216a55a.tgz","fileCount":6,"unpackedSize":217703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7lWLCRA9TVsSAnZWagAAS/wQAIOBRMafBV2sgMWOuWwg\ni79dOhlNBYQXSJt4wU4d0fFVn0R01Ob7GYNFpfoDuJcLf+no/F8M/GL7n8C/\nrB1UL3LsA1UVNQ2mu7AgFbZ7qjtqwtRzC3fKXo1HMtis1q0IY50W1VTZR8cv\nrsybdXctK0zVzwcvTXRCw6YcQ/zzLLnemdgmWEIGI3JohufVMiZKG/Go0xG7\n7noqascYXHEkMm113/s/SSTpMOhe69XulPeahbIqONriViZukFJYHHeZIzYo\nkX14+OQhA9nOAkO8/m18QLEA4ITQAunqxXsNmgBWWbwQ7SDMjM7deIUiNOW+\n14b6VircKwZVeNMSnliYyBXdKp5O/ovyL5fT9XNp+SXLcmqWoLCgtUrzhGj6\ns4hX2N6ABUP1DDTGUaKcHAkMi/d41aVsZs071fqL4hQD0KaNMYsfDhL/PMcx\nGMtIuIuDEKncFeQwx0vYDOLBWrC+7wPNx4lZNIflz54kv/dyjJabitORCYls\nLVmf7DrVQZ11nUVR1pZV4ejabD+YyY3WUYzVvAOyV2IbdOD3WzdZBMw2GZvt\nWaNkJxsuQbQ2jkNva0FUi1mq3KRpuVs/osGUWWXFuY4atNFxKn1tyrn8UEOP\np550vTl2FWESm51Yjyh51t8w4mcvV0cimbVA8klXKOe53lnNM6XL43ZWkwzr\naKmK\r\n=Hc+p\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIQCSa+7vmRWdVQeKyy02UTe6Uq3SaXnHa39Vtev/1f/XmQIfaLbak7g+xum+3iUAaoBSst9ffwvCas3dDm1KkPbHIQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-216a55a_1626232203135_0.979668188656772"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-95a03c7":{"name":"@alphaflow/resource","version":"0.0.0-preview-95a03c7","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-95a03c7","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-DnU52ZdRUc1cUoq3w98AzkcHCZ8l0okQg8IhRWmbDoOgYn8D5juLrud/k86ki9quG1SYr8YC2qWCAacUzP/gBA==","shasum":"d3b81f3bd37cd02e088d3a24dbf45d00d84c58f0","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-95a03c7.tgz","fileCount":6,"unpackedSize":217703,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7ljyCRA9TVsSAnZWagAAUSQP/1WvPj9HRkg71W7GAaXF\nOE/ZldqBZOoIdPT/ZW32L0W2pwRctlSDyj/nhRpgrPnTQADnxyBUTZh9LcoY\nVzWVBiC+ihzuNOEFNeoNBRxihFaCUSsodTfNY8ZJNuwCOuwyLQEFbueLpzPl\nGTlH2suIWpKnANc0pDW+/BXTB7v7Q1VGKomJqT6Hnub8Bri0dZ25x6HR1uYs\nMmQF0mz10dcSVa5DJ6oFUsytVedtEwWHPNF7YPVQ1x/m701er8WLu7URt0QV\nfyVDWN9dlnJudYiuUIJ4A1rEBj1KB+/a53YAVzfjgxYx59LczX2qDFuYY+5q\n7Zc45vGGQ24Q+h58WxqzLO6gAHOq+DKkblQhYRkyhI5apI7eA+HVqbEHfj7E\n6eWqmmt/ey6kj2LW5oCEuy6pE68gB4UVLoRYfRL7x2ktRO594KTLM+oMVa9p\n/cHH5ZoD6PcF0/I56Ew3T0nGMl05QkvPMvOFxXCe6CE+WFmJcnAV91IWxixq\ntHCcd1pUEUkiq40wMicguUW5Qe3liyf36twJZjz9bHF3i1O80VB2+xZyw8xo\nxIeL4WthNKcszLYfcYiAXQfIwWVZCJJlmp5B50ZCiNQIvSisy3PV2D87TbUF\nr9KRf4sy+li4lBCYhoTuonhPm94u395LqcLRF7+K6QCaNDLxKH+uu5jNs/lT\nhdqK\r\n=wQhy\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFyra9WTLd2/ZlfsAbBQzrP7hshJFdUtCKjiNLnjILE/AiB/MvY9MdYMcHXiPKUi63q5isWThr2KuPzFwIJL9uhZsA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-95a03c7_1626233073797_0.8345203745926508"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-57357f8":{"name":"@alphaflow/resource","version":"0.0.0-preview-57357f8","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-57357f8","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-7UCSfi7hwUVateyjgbTe78c4MDNKBxPWTEvbQCH9eD88pDTo9fuRQ/N/NlXk8F4JG+R/O55Gf59x29PV42PwMA==","shasum":"2e8358b0def539f7668ea7d65f4bf8c46f98a7fe","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-57357f8.tgz","fileCount":6,"unpackedSize":217883,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg71nhCRA9TVsSAnZWagAAeXMP/jMrL4KelXCzAH3u/GS4\nkAsFife9ifDxP+x7D7yqW7EGqfErmPMmfgyH4rAnCJZS0sYQqBtBwqtGtaIs\nqigtRLVguhRiWmNtFFoAQPb3//ARs//THXktrSni4g2tMwezTZnEO3fpLtd2\nbyjmX39yXvsiThlQltqR345LLBkODgd4UhFCX1b+DaR1YC8AoAbaBjPB54+3\nkBWNfu7a0Zx74oAdFJXgp/dOq9ftxqsRt/nd/uPsSkTLxprj9LqF2Z/uj8HT\nprQji9ku9qHbmY16BECURyaSeGJ3xkBho5unLgkw2lgNShK/oVU2xrCRCqao\nUJOv+w1+ZjniI6OZ4B+arDKef1vvF5668qQ7Kzt/Uv1LICXUvx2Cw76iGmXI\nDYnFzCYyrMxU/6JKhIylV7J0JBg7S614boTO3OtERRFwy5e7RkjxCTi7XK4a\nm666lnd4/xq74nO/rS8XvJlj9RVhHPd/givVhKruvsFJ4XJjN0gh0m6v3L7E\nV8dxpjs2u3u4VVx01/RfEP3jYWM3rhdbZSj1u3cZ6BRWJAlH3BEWhEMPaSuU\n7dYBCiE/mr3FtrY98PufSJB2eheTEEvcnEWJk4HXYsEsHlvY7g5RA1DwV7Lm\n+QH3lcBNPwfSwLk3IEgKuB5s6ey0EHgRFoSWeCgEkKzFSh6+rZ0m3YK5NmYK\nGQwQ\r\n=twRA\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIA+N1JggYyBhlkNqyweuK0xseisBKAOtWveEvL7U0rwEAiEAoQss/rRBayCBWzzjiDN2oGEeIEzKpmdL7xHo7zV3Ew8="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-57357f8_1626298848896_0.9154266193457703"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-348f929":{"name":"@alphaflow/resource","version":"0.0.0-preview-348f929","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-348f929","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-SelLPB03nRb36XrDCOQwOKPFCpqpKDBj+0t0d6C+3zddTFgzdQC4IhzD7yACclYDmDu4Z4DB4k9mC7PMGnxQkg==","shasum":"716c40ad893ee43add4c8b9d86e6be2081e67c80","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-348f929.tgz","fileCount":6,"unpackedSize":218185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg72sbCRA9TVsSAnZWagAAuVcQAIUPhEDJfJ6pChVTvAsK\nEDFRyZC0r1phi7WHo/jzb3AaK+69VgC57GELUxFZdkuxBFu+2fyoviCkXx9z\nZf1EizDsv0CcwGLOpok9gBzoB4s7UYUlVBH+olcrDsR7Xa31ufajQn6w9zSw\nwrP8a1hBBwEaDFjz4MQVNsE4vKrEoZETMvJsBzIrBin9aokP10P8mXUd17Ix\nKFfBBA1lDL2J7YrFQrqroWtBCb8Mhra8xGXgKL+tcY9SnrAOtR1K+MboCiDV\nn9q0fWaMwpWoVcd3LkecDys3YDlqtlvsag3cw4MaT0DriCcLml5rd3VsNbJ3\nBFH7Z10Qd83mbd4q09GjAik7X3HiRBKo2I7QhW2IJ3DE1uKigqm1+cB5p/an\nztPxGJZpIz5qfUh2bx1CAx/EVB7lGrOBOdF3DnbHp0KMUz96Y3DZjG1P7V6L\ntCEcDBKVfshbkgUNE33L4xJPJxmlgwl9aa8f5CWl+YaTuRivZIgnLToWtuwE\n+tQh25+lGnpMqaJthI/07k2Cbn2DOxTc5p0E3n8plVvPAQ7N6mmxpysg+vBE\nOGYud1kQwwTuA8cY17ymkggrQjhbpbRTMw2ySdPwLv6HOmvtwAxqwVd4o06r\nQpeWLOrMTo91/4/GD67YTJMABF61UWBGszzz+JwvAZe5uZ113qI7at04xUHV\nMVzF\r\n=vN4O\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIE5OnCGSMjxmA18BCbFxmuVavqzAxNY3iUdleDaui0GzAiADWhh7J4oIJttYH0tnLlaBRthI05cEn/k5YPBXw1xT/Q=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-348f929_1626303258797_0.3912121938626141"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-6ccf9e2":{"name":"@alphaflow/resource","version":"0.0.0-preview-6ccf9e2","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6ccf9e2","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-vMjoYDLjA35me52v32BLrUlHAY9nYppzXzGWTrPiUOQSZgDRbsI1LmRXzZfC/PToyKmFEEUZUD0Im+g7u7yFAg==","shasum":"8093edd1e7cf9ea12bd6b812c5a213ae0a2d11d1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6ccf9e2.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg72/CCRA9TVsSAnZWagAA60sP/RpUa7IB9Wa5rz2ZcxLg\nXqMUl3aCpG003BN3+X8C+2Lo64YnUU3vt394XFdLRSwHakUFUPgx0N9jPAZ1\n6ZeuPLSlJMczBWrznCaDNETdjhQN+n9Z1gVz50AtfqtsOvryUcm8OKSGoHxx\nZ/CGevP+NokU7m7WfACZOjMiUY1bsF7ShJEvIlDGWhjGAiIV4ZK9KgVwGgxP\nJ9y9h3oXm6hxgWIK6OVfsel2EP57d65E3CZ0SC9IT208NrtcLaPwLkh8QeyM\nPtvggyFyesgl59Tvjhznlg4uGRFMmXOQbdYG5xEdGeoFf0FbxnaWF9Ki4i8s\nPdfgIVmHmQD6kn/BgiMBr3GItBIZlQv4jfGg1XYx0TrcbiXaip8QKen5foCu\nOVSANnYD4Y3BgSjPiK+igX6kL1UOsyMAnevcEsCtpEdV2j8/2pWnYpclmF04\nGr8uFQeboTVTTnVbmH3A19XNAWYBdnO1iPTZnydsmi090iz7iMr+9dZWLLQY\nIFBAJApQjLEChWSr4/ygolCdIBpY4nPUg835iS5z0uLKTBMm20Da/uVKNULu\nNInr50F0dRvfijjss70CUzeUGrRmtyAPWaqULJ6hokzNGieieDOmHTjb9r7s\n5WqYWnl1EKUQEZHADcBsxEk/YKI32e+e3HJr6Zxraj07lj+c8Yg+fVcpFByd\nGR0B\r\n=7Ckk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBK3nxqI35WL3UM1Qjy9r1Tv0U5MGDikobe5RSW4bIo0AiAPRhCb+VtFN8yrIIYeiJfmmh0HxL1SDeEGP7cC1HBqVw=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6ccf9e2_1626304449592_0.49737126803906007"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-204f8fb":{"name":"@alphaflow/resource","version":"0.0.0-preview-204f8fb","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-204f8fb","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Ztgoe9mfDQBqgdk+fvRJRQaiNR/MuTkZNC5Xj7xCkkoKQjQjzBV03jfS+NAs3QsxpgKl9HrfLl4SCAoQrT2Tig==","shasum":"6facbac5223632446bb4c0c7743e6fc694a11ea9","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-204f8fb.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg731+CRA9TVsSAnZWagAAiQoQAI4EhuPhmZgakRkbB9ej\n9qdfhHQRNuukBEDlIkFlBmU3PqNIl/ohBxSbyYuobecB52Ag29lZfyVsY4W4\nqCu+LHrV2U419eX+j6I42dKsGrRQGq6Qfh6r4oHXT0iT7jksg2FvDDpOgUkc\nqHWULrr2/Ybc59yWGB6gE+gKqLFrPBfg5dzSeVlny0+YluBYRkCC6JnBWkzB\nqQJKcoNnWB5RhGN4saYNSfyYHM0bx7GPh7ewinLSO86w0wzOTh97ZBC6sy8W\noFEr1ZepZOmZ15zQneai4WjDtiIRaYl+ls30JIzGCCVMNDZhBMChMR9Ka5kW\nLI6OK2Pd3+8/T45V1xtpDYfVsRD3wiAzAZ6o+WJKiG2ZqG8deg1ZzuttHU9G\nKBj3xZWr4z3B87mNj38JIMl5e5S1PLOciKQwNADtz0YPhZvFTbNdxuvzBAxP\njb6hvtOcPRMybgb64WbfFB0vWMP8I5YXjrl5tkE86E+N1UsHl2OBvi4p15ym\nJ3OyVF0sGLDknT/dJu403mORmeqg8Inz+WfUYF+JqqWR2FdLtkEW8E05w4KI\nYElho77pRw/IVNdpBcVaVN6Wx29eYXIcLhyDzwFzoxyOz6bX7sc9G5/uM/34\ntlyNF0MyyMaivuseM2x8dcQiB8Q8ID/EaZfzU/yhw/kSy471sUrW7dc9iaLQ\nNiOR\r\n=31lp\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAEfq2+sTnQ4mpbpSCxAWYyTXfS7nkW+2S1r9qPUEUZ3AiAC9Qa+hVcd1m3XCXRsW1OMmdXguNlLrozStMz72kbKSQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-204f8fb_1626307965494_0.6905096063608946"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-6067391":{"name":"@alphaflow/resource","version":"0.0.0-preview-6067391","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6067391","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-D/r/TupvU8KMqhc1acxxPeqi8zPN3RHAjueCFjH8273adwNWjPRzzUuwZj8bcaIjoYXhwaRnzilwgGQE/QHRXw==","shasum":"94d594d14c9b83860d4c36fc94d074fec3bb6ec1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6067391.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg732uCRA9TVsSAnZWagAA8TQP/Ano+Omqfxz6w/i4crzn\nax3fJTEym0/7/wfNwUSNqd0A5TSeLzq7Y03r+8lH3B3PlT/edM4AvuUbFzjp\nkNa420srQutRFNNj0cP6qyrQIo5b5KOrmVEUKXRh1FM9deRr5mw3zVqLmV72\n09VAfHNV86Dl4dRwXgAw29Ki4Kv6TNDAKieuIDQY+g9rPis2oZMHg3p/C67k\nHDuF/Sr0Y/sGik0OHq5ZebjKc01b5+cCeIZtrVBmfcmxOCk4fK7+fSASez8n\njbhvUdp3nnnuvq47Wsc5SNmA0yVR5fGCPimWPTlsXXvrltEnu/DTdduljwFL\nuXesrOl4VJtiKBkXbdM0wmgDuCRbyW3b3uCeoV4v5BbMBBnVczf38LZxIKTM\nLou6/dRviZxkpQQV/KvKcMbGH08azlM+SYiJOMaRCVHg/sHZmc2A/lErY+l7\noyrWWSGFZ03FgSOWgrjD+ncOQ6IJKMdvaXrucowLGcRkJdSwJMPBzQgVMzoZ\nb9nzB7wlIqHNDN/h0gt6T7ulqFfKXbz45YUrrI0TmQLNwcolCyyaKKh+uzo6\ngqCPVln4zzz8lbXaWd6tCrrbK65D1PCoQgYdZOGOfAcIf4kNybHPU1BWmL4F\nt+XIrqYXmHZu9CMGE21ep2R4g1PwJaFu+39S562tpfk+szLGz5v/rAOGObL7\nfMpn\r\n=Hj6C\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEMCHxVLUv6VNCzZ3JmwVcbpGNuxDfqfLBRKM5UBwqVEBNYCIDNCUWhA2vqEHGMKz/d1vMWQ2u3+KHKJdLqEyZLOdm/d"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6067391_1626308013566_0.7394106890166763"},"_hasShrinkwrap":false},"0.0.0-preview-83ed5ee":{"name":"@alphaflow/resource","version":"0.0.0-preview-83ed5ee","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-83ed5ee","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-VhHW8kNBMg6iQLtKwvMpvhbN9dO0vMgfHlUxydB6Bfh/qFsjy8ajMI6vd+8sNphqe/ddQLn2mBvueNPwornbdA==","shasum":"fc1161f535401981f43544d8c8cf60e3442463f2","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-83ed5ee.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg74ClCRA9TVsSAnZWagAArswP/1/ZOjZCzXiHhV3rm8Wn\nbiwShbMNhXdcjBw6fdbICYjfpw/ZAlQ2ow4ZYGk4bT+FwFrNaaQqRv+v3mJT\nTYg4Uuk+TMSpTrjfziHcE3Nw+YgTtbWiS273WbFdggMV0sULJLdBaRX4bqNX\nvOtruQQZBNmhx/9AVmURRJIr5lPs24zy3V/CibG+kilB4e14iakFNOz8T087\neAC1KkaB22SQ4o9hie5rULUM65gicnArVqtL1NHJK6rhnBKm1gWONlUTOJMG\nTrDeCqFju7soCRSzDXWl75rP4OjvJ/J/vemwrBjP7TwSqqQK2h08DYGqFeE2\n95PSC3jYKzovtqEzJcRnZMtytQkgPURgrRsb0Bi8NYEJSRuHLVGSaqsDshvu\njiEv0HUhuhR5a3jdXxi2S/EPNXsXMgP99kmM/LETsXh9eZ/bNq+X9EPtmeE+\n4D+s5pcGv7eH8CDRgNQTW31yhHBvEOLkb76o7aJC4Ljg6cd+HiNW0d5n6piJ\naq2JYIx4vk6LLjRtGJqBg5h5IyuFhHfwxB6mij3BuJWMG69cPPapO3oF8ylZ\nUwbz08HsqXoGh33cX+kL35xueKJvgClu4sXT+AXAsTk/t0NTKILZzBpWHiu+\nZfDZ24xf/k05ChJ/QYSsOOnt8gpiN5+MwzK45LJdjHF7/st9Cr930NkdEDx9\n65PX\r\n=alN4\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC5fzQFAfhn2Bg4jpPSQgMnSwSUb7iOIlkKT4PHNiBk4QIgZ3WlF/GQxkHjucnoSP1+6XCDgoJXADz04vy/wvbE/1k="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-83ed5ee_1626308773030_0.234020267996236"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-0a5629d":{"name":"@alphaflow/resource","version":"0.0.0-preview-0a5629d","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-0a5629d","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-QaFciZHaTkqUJDwtc/Qx9KdDqgqui0jyfgDkgDf5808Z+Xk3N8u2LnbbS5G9sHmu+p5ZauJ8AwI3VdkzhjKuVg==","shasum":"4a362d8ef982d3dbd4fc271c74a52d939ce4251a","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-0a5629d.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg74WsCRA9TVsSAnZWagAAjX4P/0OJjn/AfEeI//+WSSk8\neDYprD4QZfnTi8CzYehSgHAGaMlKRRlFuFGjG0EzYVJnfx33l+ZcbBTqvIWn\ni3vG5GNNWBLTme5IvaG72ERdqX6mNGy4oM8bLXjBNXvkwOqxFujtLNtXJxi8\nZ5SIRuvmhxBJJcvYpG9zeqo71E/Fl8PAoPEFTkoATnDGgBjyBd+1fmR92dOI\n/yUOHH6C/BKpg69JDmbKRgV5yMw5OIY7Gj+cP93FPkYu9SfEZVmkJT1J1oau\nLLyX758Mxt8jDXQcu6KwPsK6b9kYsoYhqyLiPF5NtOv8QjsWoiruHstA1WCa\nehNodrsfVujVDi/zv2Mbv8FIk1YUKLlPhjal45GrTFvxV4j9bunnUmLWlHim\nstzRTaAtIIVF/kgD9GfNILeMXAVHqc5cDNDrXEPj3OtHlUHX+jt8LxjUQkxL\n0SJlSLApNm2kK2HEFKzlDBvb8zFDbs0ZyssNJYic3fjtRZ6bfoP+cPUfVRFy\nYoOKhD/QQRFZjEw5oGguY4n1Q8zknMkdx4twchChxu2LibmjHsM++lTHSP8l\nGSbADr5xQ+LgW5RcXb6MAxQRaHMmgvYwyshKhF7IuBVghCcyUKnGTz3+zM8X\nkLB0tL+f1tiPtXGB0dA0V0E8kf6CTiUE7BpqZGtpe+lEAMYznVjl5n0xlYEK\nbROb\r\n=NaCv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCLHYLOdpCYeYlG4wm0lpu+gREKRlxEs2HA5W8yQz4FTQIhAJiPJj8Zmso35tqcDy69LLD9hHjdgvAu/ZWcSVmD9D/b"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-0a5629d_1626310060571_0.5852787672531807"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-6fb5918":{"name":"@alphaflow/resource","version":"0.0.0-preview-6fb5918","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6fb5918","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-CbKA/uBbepKkPEIdLcf13mvcZCcqmE54mR6WuDdQoidhdfJXGPzben+N6vtiFOasvd0igTwZBHPJ1AWChbBQXA==","shasum":"bda932c544717d8249cf5e4e3eac6b780e2a78a5","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6fb5918.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg75oKCRA9TVsSAnZWagAA4mYQAIZIhAgJXcxBqctkBM3T\nkYG7SrA5peT4vMcd81R9gqII3OfppjcMyIZu6pZ647a//ZnzLbbwYEljgSsv\ndirV8u1J/TDJfeQDeb7PPcaFefUOxojVbKQHyHE4yXPRaYHhjWVw+qkSXnnQ\nbKpEQyyfBC9FeGy/5WGs3yIKcvx7rrPBLqg1f7V1wUmKVl+3pd808ShWKr1B\nXkzWCuhZDFEpVG9ZXA7a7M+d+LnBxWYRoqkqxsYRX8GhAhrhhWSNlbqnxelw\noAytw5QMkau4k8XRBHo18MQfzyeAfJxaaWcqwoEEH4P+6f+RLT3Bo+oAVvhl\n0DKEDHc7JT7zsRt629vF4e2tY4R0mINtkGiwKCoV7ZLtvBW7s8BPH6fdCdOE\nX9c3V3OXqrkRd+Sh+JlO/u0jVgl414DIodBFF9WBIo5DoihmKmHCD5BoYRlN\nro3T03HtIW/olp1vcV3IMcx0s0g79/xElMQEkKG3/P2lpBRLocSoI6bDI8L8\nRAIUoB3MmofgQuUe3GvhKBcFVafO74SE6n745mdMiUZdvJZFyGcu3Q9FZCk3\ntQc9Tnb5QoJcKY+6TFk/ng7jakOpuC3zWkTGN1FbgM+WrrWfSWUQKis4VCmG\n/9yoSjHvjPvV1vdb69/p1vBHGzmCLJGXb8JiMBwzpfQuQJr7ciFV9nMsDQN4\nD5l0\r\n=eLQH\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDfAfQDlNd4w8M3M4sv6bTws5bH7ya/40gDjg797G9i8AiEA237ke5F3uRVFoSo0rN5s2q/KSvC7IjGTkd/BB276nMg="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6fb5918_1626315273517_0.038357604291856084"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-41149a7":{"name":"@alphaflow/resource","version":"0.0.0-preview-41149a7","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-41149a7","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-mCi2cvKAp1lY1UBgNT5GxG5VUk+plevcAGzrdPzzLqXJCjM14shWhiHbGhcVZVJuc6//x1IlxIoPgP0xvE5HHA==","shasum":"c38c2c91d1109e7c57077c60db76f693a3a8e8e0","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-41149a7.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8HdfCRA9TVsSAnZWagAAjeUP/jDTZM/R2IQc9WXqbmP2\n+kYfdaHOBQfmk4f3tz8e0JTtxhcUocYCBJI9grZqnJaCimrXLsrR9Zba0qlg\nL7qVyiVnO44S+QUyksspwd3CBK4XyDWhAodGoseWSPpDcrX49GdBbbOQM1IZ\ngGbeZgl1LMLGf5ZtVpdZio6aLLxkFC+ALYBhrWn4b0piyyVHoUFXkyCnEKUi\nny+SNZtesy62kswV7xqaUsT1vg6eM14XkUkooX9bEm6Pa1pij5AVowUAzC8W\nNU2wIA4cXdV+iTms4s2FQ9cdY18mgt9l3bl5gQQ2jf9Ylr/O09KXS9NhGpvt\nduQy0Vr/aAyqeGYHpahcj8R/jpWJSBo25SJzR18znttVCgZOR3ySICbFSbsJ\nKFYSmbHQMySAYKegikEP6CHvsmmFSCu7Y4bcny0Io8Unq1H3cKAN3nURZsqF\n7Cf8wWTVQV+adkziG5EbK4baaISrQLZcrfNUQBmV3XyB3r51isFlv/esEVKx\nW77Ll/x4vr0ceCpJMDj3QqF35fpLHWkMtV8/sTeMy1jKp65MkFQXyZuZFLnQ\nvalaaU8oUqgNqZgOa3e/49Ny++0ZIoLbzPr+OlEIO5HNCh22MgygccIFD0tL\nO78m07UduwwLpiAZQTwuqllGEg+ngEFhD9PfpUEHRG4F1vLDB7KayZtXXmU1\nH2YS\r\n=lieg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDXXRL0XUT1NTJKPUhZfr38BUqQ445l40Yi8lOua4W3KgIhAOU9Sl0dTn8/q8vRAzXISL3lqA8cQo2RG0s3W0EbPfXp"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-41149a7_1626371934590_0.06442598171431668"},"_hasShrinkwrap":false},"1.0.0-alpha.19":{"name":"@alphaflow/resource","version":"1.0.0-alpha.19","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.19","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-7QZJzSkAEeeFGs0Nr4Dv1Jkpz0fVU76C6QmsBFMExFLeZBrWVsdQZ9HmU3kjx1qDiAxxlaNa4SJXXxwJUSJezA==","shasum":"218b2974f41014e4dcf6fe04ac638a59a38dd679","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.19.tgz","fileCount":7,"unpackedSize":220925,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg8ItaCRA9TVsSAnZWagAACAcP/2fiBtZTib7FHuKvQSSB\njwju7+bU/R/0s9rbjxe4le5IHxGdFUFv9zcZmWKjn0jAE7K6xUfWMJO5+Cx3\nw0t8FdbldczJqq0Dlxyiok5lSxpbav/AGzFcdY5TVmH3/nSmsNn6M8xgmx2O\nPa0xcl+pX6ykcXB7c5ffLZ75lrhkf1caV12MLazX3YoWRhXj9jjfvnvf/vwF\n7urUKtnsGIjYkirkCb+J2mZnRe0kiHstxjh1S79v5fR6etzs1g/pUd0Nhem1\ni7vDrzu3goavQvaPw87bkOM088IvMmuJ9y698dzttxf+7mkCObr2U5yDVniC\naRLU6QrsaEH9S4jikp4CaXaO9dTU69SK3IpCKEVeZPs2PCOnDPoGNB7JH52R\nwOn1Y/E2fEIbiPvmXKhUfkL76hLWaebubBSHnMoHOWquKKl6B2WmXUOO57fH\nlGdhPbsS0wXsP1UHxdtDs2wzES/Vh91xtRP6ffUG4IQw176L+jYOMm2J0Pe8\nS3WsK/2Hm+8bwnJMmpRb8uuNygNjPZ+HyCPJ8tP3R1SHAX2nxqsUOl2WoZp6\n2sznzbjmefj6g2fs8HYxoZkOoEpdCcBIL/nesVRv/RmAjUBoFjIiEHMkidDL\nffeJlgWZ+PO0HWEhI4BlwBGXENMkctiOOXgOaqhRUCXkFLkKrUukjAamdd0D\nVmTx\r\n=RDbN\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDujjII8N7kbaG2DGQJtL/I9HfAun/6a6Su/Zoe3yOfGwIhAKC2x3dVI9W+W/Y8CK+HcAnyb4H9v7qe50dIgbIBAP/5"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"tony-o","email":"tony.odell@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.19_1626377050199_0.03723923652405836"},"_hasShrinkwrap":false},"0.0.0-preview-448c42c":{"name":"@alphaflow/resource","version":"0.0.0-preview-448c42c","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-448c42c","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-i4cYHA/hSqo77UiRL88D9R1WMTjkVrNKCCyPvUX0Qt7P4aFNsWCdVcK4vacXVv35CkknmfKQio6axBl7YsGUlA==","shasum":"8ca562d83ef4db6e004d06779f3307d626930eea","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-448c42c.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhCujuCRA9TVsSAnZWagAAtgQP/A/+jc8dxBiWpy8LJhVY\n0Mqx5vdVKGeojU7RxwcGHW57xAHlKkxRyIU5qkxP3MXF1EDFIaGKxielvUCy\nqlxo1e6OrurfHZkManwMdz+E7NRqrCJgOMPuhyt5wGaFWhxTWlmO7OZ+OQAM\nZ54Qh37h7BszlsXwNZfnrwMywVvwIBJ/3/p+pSfLWrmFhwfgTrk1Drq+xczL\nL1IREBeAA5TdS9LD2P4m8MepZnIYcWoTOL2F4hS72vbDUKcRKltxxi5Ye5hC\n76MscYskAwiIEEcWcRctKEsZrkLee5w5Lww3bt67k+hwkO0F/GZf+uRJ3DOZ\nFUSnthoJbi5yzGhjimjVN+Sy4LCyU6Iq/TfABTvyC/nCW6gLDjo57gR4hzJT\nw3N92rMruG1CKmFLGSJOhoQnY94YfQ+m2RiW0S33ybY+0C8+YOzbJUaeivnA\nVDk9hIvNz6KuXETnC6wsK70jZyoqSF38l5ZXY2aptnDTppcwLjbwEICc3QpU\no19mrzMfOwi7jXYnl6a6QCVMgLPZk/t6QP0eRiOd7iIVNvE/Vkey6U71TBlz\nvKQqI7QYRdNhF+wRsHI2VvQhswUXoAWLo/gSLS5KGeqpgLoI6ow1sTnRoKen\nUZ0THqgfSg3IbVnjLXiyN0xY2mDzuB1JjOnQoC2+F1fkAM9Ll/LIeKiioed7\nxNZM\r\n=5laD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEksbukUrCy8i22xj9+RE1FiO8jKdw6igCLXk9/GYEYjAiBAY0Al24NBO7TqCeFygftOLBvwtZ9FZWNNGgExR3L99w=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-448c42c_1628104942546_0.9715812643558699"},"_hasShrinkwrap":false},"0.0.0-preview-87de44b":{"name":"@alphaflow/resource","version":"0.0.0-preview-87de44b","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-87de44b","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-3WMN9DiKjFrukqlhgRpMMLOG+O1xjJV2fF/KSg63sLlYUd7DH/wTK4QNP4f+WVFIBf03hRmxRQ4xYZHU8ohFgA==","shasum":"62d83265f4ef10fcfd4b03d125ece09a4429a758","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-87de44b.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEY4fCRA9TVsSAnZWagAA148P/1LM3xwZ6FNIYt1padop\n73aDeeEVjQH2kshSnIXFNomFFOhA95EJk+867VTtAD22fwcNEiTo6dkknem/\nmfevCPT0ARvEGUAwRaHJmUo+sZ6cqNqw1pPo5PlNG/uS6bXMsHoANvxHL1WT\n0NlFdQQD3/TchXvKt3GPv2lMffvtQA3TIZ5bw0a12QYwafvmld1h/dH5F9oK\npU8Eg7c5gPlk5cgtEyZvxkhkLaZP2/2+Bx8LfR4yC07xvvjcDil/qNpFozQ9\nmYPqTmKimJlitnipc4qBj1QcLY+4EYLo37ev2ggmXIrCVVYkao/N5l9IsjUx\n/KFB2ULzWjwoYG1yxEHMB6OnMni/gYFVM2qYk0tns/qLWcRpW68YVbU61wc/\n6XD/c6RyJr7jgCpeZ+5UjqYOlGaLnoVD2gd/zUozT3FzN19+cai0mPopXdAi\nemOHHT2SyP697HiuRvtndC0nZQaZWEwf4BBLrsDObT/16/JS68ESwk2StHxT\nsTDuAinM8yOxwRFsq15hDcVfiPQO57H7vmhi6p/cqiDME421fCPIdnbWM2vq\nm/BrlTlacnUhNqZoZ7Z5JmSFtoyXC83grKP8NUY/jzfF4YyKcrLiZuaNSJpa\ntbZpmHjPC/7s+NOG22q5HipwN8TgM5RMXJfVTXbm6lrOLeN3lF36jVcfai+3\nDlfg\r\n=jorJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCEJgYo3/VRmPSrXRl9Mcw1FWDZ8yGlJIL7WbxRQG6hQAIhAOylphMvzx7vVO3B/9uma3tBd5LQBVeDLly9Dzok9jTw"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-87de44b_1628540447549_0.3079650724332854"},"_hasShrinkwrap":false},"0.0.0-preview-028b435":{"name":"@alphaflow/resource","version":"0.0.0-preview-028b435","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-028b435","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-xK6pTDGpjZyUinRu0XhlcXD8WqXqTfcAlNFdrBL/ytoRsbPRVP3HSLFT69g0E+MnQ9/Y6NX6iriUxR3DFheFRw==","shasum":"7c56c0db9d793dcaa1d78e259c4d7d8de1e17e6e","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-028b435.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEvcCCRA9TVsSAnZWagAAddEP/0PQ4/cHHvL25VRzCJv7\nJH81ZDQiMCOMPfHX3ZAcNQSiDSdhSoeARyUx8p0ui459vzujWcZUiiKnf2l8\nJ8N/Gm1BUjj0W/YMKDulxH7yaIO0kPCPfxi9gw6jDXOOyxBAhqQdQg1BhcWY\nd41iaBDCQDaMibZPeJS+2JvEkPF/zq9aacbE82ZizTekhqYuIqaEsuMxRey2\n90CRmQNefrJ1luYAwA41nZtcwr0sOmKVYsWqWC0bNQwleuirvihMhzEkVY4/\nKOgiIrjQYV7X5j2W7nToPHrYzoQwTf9yeaPmmw/Bv66yn4PCow7yeoShT5aw\nM7YAhHS0GddgpYyp1Q+UD/t4QYb8kcmsrBu8SVvM0eFFFOlxNT/8/EtJQyO0\nePwlk568YPh0TaQutef3cmD7K/hxFworM0uPHDbiVy+TLEbEr3XWJAG0qC01\nZfA5AYni/ddBPxVrhDNdqH6OSbGGKXnnw7xiqi6nUH3vfse4GzzWpJcLH4tG\nZ/jTqlhf9KbUiZ1X5+D3b6uXyCDep4CIyvk3a+ba+G4tbxIKQs1klD7lJAtb\nKF+tJaPFqmgo/ykNK4OJS8Mr4DXdQx14APkyRWNHeL7FFG+unkO39TkCT4st\npZ7zWBLNsc0jfyXqawf2pWD6ek+lir70qKOVzD801y5/adfWxJpGtA9lI02k\ngEeE\r\n=YQ+U\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDANXUYGjdkLccq6CZoJS30wspGnWL1Ir7/lz069AvCawIhAIPqJnPH4qwuVwdrPULWkfufYNK+i8yS7SwWMAMDE2Jr"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-028b435_1628632834756_0.15343551466598337"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-6b7a6b1":{"name":"@alphaflow/resource","version":"0.0.0-preview-6b7a6b1","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6b7a6b1","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-OKtZt1cwiXHFxqX509u/RI0BZolAY7W6AEuOU53xn4jSbtSoxeeHOTL8ByrhE8bvi1yd3CUMJztOruHlx43rbQ==","shasum":"5e6f7735a0b132044fcf93ee01bfe2c48651c429","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6b7a6b1.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEveKCRA9TVsSAnZWagAAZn8P/jYNQq2A0L8vHN7GXByp\n2ChYmxgIdptWQTcsQHGMgHAhSJPWdwnNaD+KpjK8RV7ofxmSrWa3sTPi5QiN\nLH7xh7XDVXSoIsO/rr5ABr0ZhnxH9ZT5B+DhV1fDXl3Go85uH3EuAX3j3tKA\nNKpNtylKhWEOOzjh0jO6S/GNn/0r67S6K5JR/gvboB078RV0VKJrK9nv2bWL\n2VTwwYtquwkTr+GM7wv07UfYYXFdCUxqMo5eK8JORdsiARZqKeEAYES66W2w\n42/1V99f31hilCUszLSFQsWSxs4iTzzs6LNa434rQzkJofVbjwDjLMbD6a9d\nK8YE9HkdfAic28mh2QyCE+Icu3fCiUxPHko3O98uO4XvyYgiKCI/NO8WiX4F\nJHi46XTuq1j1kU7nFD/HrrJPFXhnELj8oSyWPlJrhV0z5rI2iwxv9dFfWC6V\nYTWtzr4NqTumQM3M0ElPLeMy7qTXg+ntx9LGHUWr889CEkjILVZxfeBFwP8+\n6TTXz+vuNJKpJlTAiv7v1o33UQSUdb7C1/G/y7FlBmq7l//TglGGyb8NTkQg\n0rCOnthP4y3V4+pCoujskxBcYjSfr99TSFv2w43W1eyszKFgRoX5gqONEwCx\nN6HZprBB1ImbfzCMPtMqnDLRtsjcN46dMMkkF1B9L8dL8mqdLHnnLsJDw47C\nfYmg\r\n=e34W\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCo0sGHyDAHt6WgD0kNnCjqzgGdhSVDt9LYRIY0ZpvGgQIgX5dRM8UYe2fRRfU6Q0Ny9iZTwRGlK6jeNK6trFGpFTc="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6b7a6b1_1628632970331_0.8693641367403313"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-69f2629":{"name":"@alphaflow/resource","version":"0.0.0-preview-69f2629","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-69f2629","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-UX4wjeU6UKtYF8d9WbaLa10pnYnVDQ7VdUFXWO65sWW4ZK+jo4tB6b6BUzrgvEYb6aiKNA8bnqMa4ntOqFVN/w==","shasum":"dcc8f3d50292ceca2309e94ff63a45fd69859e40","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-69f2629.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEv3ICRA9TVsSAnZWagAAQnQQAIdl/7q4283rQWjtoncG\nkyERkabEvhdQUKDlFKv+0hrwpWSpG5gPsWpNOHLAvMdkULUJJlqx8IDJUtXf\n4dwdfQCqAVHKI4oASOLXIRs20FNjwtiF2b2+1pnDQXQX+lyyuQrDykUGQcrh\nQgwYz8tdvaUZ/x+Pslv7nBtV5eSh/zIerIWvzevSAy1SfFzO1zLpVV5cOU/H\n8cLCe9xB6EBBW61qucgK3c44o8pHOwY4ZH6AtfqYDQhWA11e0vymsle07EX/\nMgAiTXSI10blZ94hFHCJzeyJpFa37TXrKAR7l2nFRbFPaL+u+IOFwcEV2FBF\nPt54cSAmBAWhedK6Uo9FRaT+npeXKeofYQCWP/Sh2yOptvOC2kF8HdcKZnHC\nRmnIbodJygd5hNeDpRzVcT1RvsbRcDnsCWyebr4vXOgWi8YhRNZImdr3NnNF\n45uJH+9gfRCDvXEjCB+STeM/mVhBoyTX0dV4CRs2gcf8DtTeJp+/XAuGztcP\nZW2z5HKowqbz20LlGlYTfuPMYN5r2QDl4EwnnxS/YjZ8Sq3T/F9jpgspuqXy\noOiLmGwpDSa/t4jH0XywERsQ+KRUOGFFf7LU/OrlTBU3jGndVn0OAadXp7ew\nG1fjnOdSipREI5ASU2MsYUkBGGsIkt9PS5vVm+5nVDmH4P/VZLtrLYsRloLW\nem7D\r\n=E2Rk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAGcqRpqVHKnlZsSaX4KQJkasmLVaM9ZMr6WNwMWS0gnAiBXYW1kvrYYOExl4RPCOyqRlEZcXRXOInhq19wp/+fPCQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-69f2629_1628634568470_0.31134711257506464"},"_hasShrinkwrap":false},"0.0.0-preview-fb27d03":{"name":"@alphaflow/resource","version":"0.0.0-preview-fb27d03","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-fb27d03","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Y2wWrNdLFGvDmbFm7TK4x1N05y/i9H1jZA9P69xFey/LY0CfUsKhHclUDlg4KNQheh+qYqA6lKuikBt7KwDTFA==","shasum":"f3e202c99a98b2815efd4fda34ec911c336134bc","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-fb27d03.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEv3lCRA9TVsSAnZWagAAhhQP/jRCSmnSBXRWYZy7PoKY\nS3iEYtlQMB1BsTJ549h2AN1OfL81IZmp+Eb3fLUZohNnax6xCi2TQXSCXqLf\n4utPru0FwxehZIGtiXVBz/5vbCRuIA6wRzB2F2d5zEIc3gMgT1hiB9aPj47m\nhPCZIa60EwWIsvXPmCbcLt2i7gHTN4sEXay3Zs1ArdEf7kdD17ogOj1Pt790\nHdSGC51fuBbEaDw8/ZKjsVyLZ3eo9HL4jjyMweIDsfAMLahcuEOxLTiXS87N\ntq+kzp91JCZbiWyBWudVdD9adlt2SBNvsP1XsfFIvtxPWQ7UoZsoFwU44bi5\nUWhj32BW0oqG8FaWbtgXSeTYxWxwMqhs9X4eiFQ9P1yYKf6hOB2i2sIGG2Sh\nz9LCLLIskBEG1vKXf2ru+EsbsAQLy173b6fEJacLQoDMWIRQR1K6FbyJC+sr\nhU+gR/NFTakP6cVptmwwQNV7mTTBiDX4OI/Ec4Q3ejS11S2+MvN1c6YddJio\nmk/9t2IRXh4jtx5Md6AQMV5YHnky3YTasrqQHkuFhMB7PWkAm5oMXUHcQQMp\nWd8vowhHCgPVOO+dDuy2/BY/paZr4Z8mpc0rL82HmDeyXLwYk/TZLGFoC5wP\nMVbHvRjKEUTvktKSW4iBAb6k3ADz/gLOVRf8qs1huMH+I6l2LRBP/FEZnS5m\nG5fg\r\n=8+mg\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGzzueiYlS2T3/ltOaDw8e1cM5R2GrEzZ4bsvu+90/uvAiEA4OGhDyYlBYMvaDAv6PVSwRKqKbAQfJmiHNUi5ICr0C0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-fb27d03_1628634597410_0.7195612704140584"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-5bae82f":{"name":"@alphaflow/resource","version":"0.0.0-preview-5bae82f","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-5bae82f","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-y8l9QOwU0JNjOJliwJ52ReNXPmzsaQIHjLLRAWPArSIX/Rw3FW5aqmggu+cktj3Hq3zuEhZJQk7jR03RDzvSjA==","shasum":"cc39bf9c3442876c97af061327b6ca8d8e54fe7e","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-5bae82f.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEwF5CRA9TVsSAnZWagAAA6YP/jmPQGXd853yovNNHrGC\nJTgcX2bTmzicpZIpP47HNphGZHRIBRJ67oTZ+lbC6yCkb5DbLXp9ZvSIU37q\nozOPsgcfXKYasygIXe7uzm0xDsol95HOM4+FdYVihcHo2u77pk/h4PERvlju\nF/+Nj0f25ixxZaWL8q+Oyr476oMWV4k82U4jep3d7SCxrS4mmq9wTWJh7TWk\nupHLVSVt28piSqhfyB/xaj3Nwp1gjTnIWaDKXcKsVM3e7GODp7ylLSkdvqW9\nKVdyoI+nDtN7qR+U7Ibnj3eXY4rYVAOJIFlzAJhgpXh/PICzbDyhCpUcTF3K\n9zo2xJf1WNY6bYsh+WUtUU6wE1aUPxbjulyuKQja71pmNpp/Te33vWfcCZVO\n+DUJumkpvt/wH3XxAqdHgQikrFiDzyYcQzrxqTaaClAfnNTggtsFzUhQDQze\n6zQqqRX6NmSjf0d6vQHSqnaEfIn0JL5eofmjeCUWp7jwJ0AyMBZ+BuAwkGCh\nr22YmMngJcNZtBD2dVGRLljeInb00nrfmogl+kuldD/5EXwLJFCIJUhFFTne\nb0nPy/AEgf95T8TN9p9LZzCp4Ff2ImJDltJgvWkigEbsh59+VLzyfmyVaPpR\nd7S3+j6pyVBXmWAaXpE8fTx82YKYmeCIdeOxMVS1bcf1TOnB1kxPiJm/bIf5\nk2BZ\r\n=H0oq\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDM3+G8K59l0ekn4JnH6j6wc6h1EkaAKaI6LuKyHdG+6AiEA7GacBD6fTDKFYncQIdjvrjrQNeftBKLJD/Ls+s1dKo0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-5bae82f_1628635513623_0.6536224569950424"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-88b6c16":{"name":"@alphaflow/resource","version":"0.0.0-preview-88b6c16","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-88b6c16","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-EjXhAGsciK7iqRBJhfDYlcpk2Yfhcl1YJOT4c2pYcfALzXVtqO/+DcsVhlT/77YCy3Pkx/Bkbir3i+0rnX283Q==","shasum":"520b193155e959e48801fbaa6db9d71b4f9d21bc","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-88b6c16.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhEwGGCRA9TVsSAnZWagAA3bIP/34tg1DBfX4EOH2AYQx+\n6vdCoKn0p5zABjvkE11bG5SY/gkMxxVPySYdUPto08DEoDvfiGaDVi4mgAcT\nuaxfpqOn6DGezGwZKM3pkkljvVF2aJ1WuMTrhe0LT4WdTKUlUbiLTg8WZBf1\n+x5CeUzP8SzVuR2JWoOQBkJLirOB7Vy+z2n3dNi4Av5t4dyjJKC1XBY4FxLS\nBDcYBV111VrsNA36SDoUQqBNWPYql2mG1UGXw8hNGbRG2Qnu9Jgk5Z9EBvjM\nTUOJ3v6rCi06tyYJtDfp8apeR+ZLAOAO8xfPMqIR1NawakUwByrLemAvMfzj\n9D1pwWd0JXJtKt4InjkHSL4eRTKNvVpFd/uZllUBFd1Hu43HdPzxbAXVtMvQ\nVuqB3Jl/wj3HzEBbwO1vJvKC60G80E+U9mJYFxxZ893ZqMP+20T2H3qxp5Fs\n0xPQB6vPd+vvrU/SGyotTfS4jR8wIEdF1NTX29wlKQeFeZC6IMqqR8a6meaD\nW11lJy5RiC4KgVtFG/1PgqSup9pffg6Bm9Q9fX/g+DMVES1cV1JKkTnOpl/S\n3IxZA4yD63LwQ1OCzCREB4v+C6Z4zqQ1iVr3mKP1/dYZGyIQhRMsupzVQg4A\ndfwZdK563DevCsccFMRgZbxyP/A0VHforCIVKxYZIfC+rTNt/35KqqI1PJ/6\nfQDr\r\n=SSKr\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICqBjbyCX6anxBaSASux1z4v7+hhjXBAB8r0j1/wqO7fAiAH0WN4UFomCV3872LZsMqNS5Zs8Zbo5IPmGi7Im+mlpA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-88b6c16_1628635526186_0.011060996725653416"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-457a873":{"name":"@alphaflow/resource","version":"0.0.0-preview-457a873","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.16","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-457a873","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Ri/sczHav5+mzlyR7CvHRAv8JTM9CYD4YtzXZpC1tWe42r5vFFh4Ehk2rHnubNGpydOx2XDrsEKn1LY8TiSWpg==","shasum":"74814ba243166b23da6c8e3202f9e294635c0706","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-457a873.tgz","fileCount":6,"unpackedSize":218215,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhL80cCRA9TVsSAnZWagAAACUP/i3Rqm7IlRy2TlGjeekq\nVtWd7rc24d3AglUmNtqp7EXUKUkMoonnFf0s3dTxloahEDd+Con4hiLVmkTT\ng0vPPMeZUYboGek25NP44Fo4IQrr5I7K3tuZlFngqym4W2Knsa3FUQIvSJrF\nCvOujnwRWNuMPlQRrZmLrz8UCWQvDQytLywIGxOP57I7ggpjE9uN136Avm7Z\n54g00fz9Rj36FGXB5F2b0juwlVxmeaKy9UB4pB4PMW6mnh5g9cIjg8LvzWAA\nM547Pm/3TDNbzk/Nn2z+/klH2wJm+Ehqp6/GEkdvV2S9Ip/UBWPGwlmLyFve\n4eSYxgJPIrlS6eVrtjx4QdDaNG8nAuDUeYzVwu4JQMtQ/u8bkjaUSguZXE6M\nC0KeQD1OSUuCx+RZ7fJrrZeLm4d28Ya1vBckJmwQeN7xk/aF7B6GvXO9twa/\nTJOJsyiAzF9AQg2FBEjdlWbkgSjJEgHAUvXC4BJtW5txA7JXSpuUCCUF63ac\nbLnS4MJAAZp3iKAUHcwGIZNtHAqrHuTyYo2NsK7Hv3JDAPJhTt605kFfIP3s\nEY5pyZxuSxiCXjScFrmnw8t9FgxNmkfIc4BqDPuZuqkkbTpzhzGWRFMX4gQA\n9szWxdeJYVYRJeVhamkPnLlr0VO78WS2exlVZiXdYJ6/0vHjVnBbyBBUB8F+\nGw8u\r\n=dozQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFmlXTA/Giy1er4qotIHqHDrkLUqgvVZ3J06GmzCA48SAiBnRV47kehII/pDrKxfL3v+FyeNB84kiujH2dm7+9vcqA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-457a873_1630522652354_0.5765064981576073"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-cf43a96":{"name":"@alphaflow/resource","version":"0.0.0-preview-cf43a96","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-cf43a96","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-GXfQB30bdlDJ1tEZuRCy7TJJ6SOFDZB2r0JAII7oYn2kgdPPycUjk6Kib0gPeyi/EoC86OhV45YSBDHLsTBb2Q==","shasum":"458880c57f1d9f121d2b3a0067153a6e3893e3eb","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-cf43a96.tgz","fileCount":6,"unpackedSize":223623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhMnaWCRA9TVsSAnZWagAAjB8P/05MvNd/+EtQo4amUMwF\n5Fg43QxkFMgOajS0i1xoJp2/woecmBrsoZoKzWs0riFhtHfc7GYKDuTXnpXj\n0amoOVuuYJ5DTQOhO5M6M6xkazRRnGa22GWcnyX7baoMjXIAzPYiXrgNfsvb\n1+EijM/gt8VhZZ0egXvMnZG29dAc8FOhQzJmrdPeeXNDrUoIJIMJAx1pF6Rr\nabzoxD0np41zA2Gz/iZrWEnozmFBUfIaPvi8wNEqGXok5leZcgSQpfaGfNwB\ngJITpiWmT0ejCYI3NY07HH0aQdYbSK13ZG563uZJwOFl2y0Z1jpiOCLts7rA\njZf2AE7YSG69Cw9tWUElNvWf+Mr3M651dKPJe+RCxyh2mqvQXSLzcsK5Yy0p\n8HUjmU95cOQStbZR6MqLdSJF+mewpeGoUImfmuFdsUfGrbAtBjjAuAGJtaSE\no/GvQ6j5nNVLbtHipznNH652LXcQGT3wdxLlPwQHkQJhP9EJrc1VfJZySZLI\ntWNQagCPERYN/MBgbN/4A2HmhdEbfCfwkN1sVgg1dfSyxheeZ4qYfnyip2Gq\nk6k4EYUkPGNBTg8ZNZe8MO6tyOXTtuOA0XpQmhcqoXGX6vDKI9T2Z0lkavQt\naEXmVC5Wo87T6pbcbgzxzdVgu/SsJweCG595IL5qbiAT4l2WKMHnY9uZay8w\nbx8Q\r\n=aGNH\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBbliyLvp0tJ6HGI6cZf8+rrcS0BOR3+mjVdUzsaz6dxAiEAvf/nNI4wOpoKSR7LnqDPSMj4Bw+1vZkOMhoJTrU57KQ="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-cf43a96_1630697110242_0.19004862862366112"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-da40919":{"name":"@alphaflow/resource","version":"0.0.0-preview-da40919","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-da40919","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-qaBQWIxFXdgNUPGzzl3fCxb/A4eOABjt10e4S3VEiLHzzla36ep9ho9MF5Oi33sCOkTS2FqD1oY/oKrXYxQPsQ==","shasum":"75f3e32a0b152511274fa48d5627e4bc78fe833f","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-da40919.tgz","fileCount":6,"unpackedSize":224234,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhOBlzCRA9TVsSAnZWagAAZFUP/0aQahaoHExOmEaMtVqz\nfVZkVIaKgy5X/Je3a1h4nuakjXrZ1LCot3FM5yo6UoxP02nYWwwxYWRKZSL6\nPQULbLTrenpxCUH/fGWY0pU2y9hFFalT2SeeQj5ynf2VWa5uK5QH9QD3Vew1\nNpYVMm2XtR/9/nWdy2QCflGfCcPUeeTXCSukc6EcOK7vnNN+jtULtoFz3Vdm\nhRrl2p7z6AmSFTtKkh9kDW2WwagtiFq/9HUXd9NiaKb1WcMG+DKZJFaHOlNw\ng1elq45HawgPxCOX62waib8eU4AxKRPQrDgkMu8IRYVnwpvrp2TMAg+5fmFf\ng+OXDtUBiyTiVizp+aAzMl4fd3KFu/LJcB7iJvr1WmUE5GBV4mBC9X4KNIE6\nBj/OLPrQpKtinXBg0lEYYV0AuD/BiBsZYiz2K6VUdKnhSuDWb1X0URUYLjoS\n+2jHPaiUZ9OFkGBFd1knAJcwxnnEGHkPE05auZ7C0COPfwCheNdbV5umIPeO\nEIq7vAk/DUaBJHYg/anw/u1tpxygTl4t6AXA7Dxd5Il3/UPTY4E70o7ZZjKS\n3PQ5Em5eDEoq8DFUjX6gltZYAivATVJHiLJYfa+Bnsy2/4WqgYl7lG6YUktA\nWvvTylfnFx44iefYQABV1TAitG3i0KEolBXNp/15/q3e/10JfNqYL4nwY4lg\n/coF\r\n=hMke\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD1KbQQpQlsxyLcXZ85SiyDzDbuwhPE2Z1eKEcvv8tjgQIgWaboWkJqSbS6zCVu1Po0zzzyKlfLGiElThQ8xeENl1s="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-da40919_1631066483321_0.2807801378556638"},"_hasShrinkwrap":false},"1.0.0-alpha.20":{"name":"@alphaflow/resource","version":"1.0.0-alpha.20","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.29","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.20","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-X/F5xkHZPjH/irpkicoZZb7EEpxhCy5ukHuez5Iq1J/Tbvw/vxR0fN6cpyVvziFXw4hZahm5G1s2U9DLuZdAqw==","shasum":"59f20b87659e244c407d323d36ad7ab45b95753b","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.20.tgz","fileCount":7,"unpackedSize":227086,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQQACCRA9TVsSAnZWagAAhekQAIj659xVf/p0gksq2cF/\nBUBDlOFYQoziudG7efnAwRcO8H8CfZpWJWGnK00gDohT/dG/L086iNC+eTB4\nOiR5wzmWnemctmNRhBcmtoGpnQbZrrV9B9dAjzrwWGmaa+RtQE/+ySYye+Al\nNo09Ivs4OiQX+AapuoLCeDt9Yo4Qg5KMDuVEUbCZz9XfhMOEu3fwTXCykiLm\ncsstiiT4lYcndJjGtcs5mMDr47nKYKIQ4DKJC2ExaOxQNfqLZw6+DVtPRMiX\nPqU8wyFaZyvb9HMCCCwxbSEdGaYenir3bAZeby3895ywGBB3iPgtjT7W2ib9\nj6YE5hVm7cn0+22RJPXZQU86o+NZo0f6xKISMGRczCx2e/Z2uevkeKniS8+j\n1T9aZfGfpdhqYOH+oVIebsP4PxkyyNw3zGTs2eNnraVfxAmtpMTfUQUjAf1z\nStSi9TTosxEw71oI7tSfGXFi8yK1dQcUw4HArmnH9RHD/r1BXMEUDaY3pAdq\nmtUWLQFQQdghqcS2gwJnc85B7HZvez2FGkPP69/f+ljw7RpIj7ALCK4tcOLE\n24psdI0UdDC56MfNGa6M46FrGY4HcOiK81BKmjavq4Op0eNTR//lCjG0KNug\nbhEI8x9aqTrf+nEvMz8P5YSVr0ZRXrVKBMoBCE+0kSEHP8CB0ZaBjfZmyEeT\nQr92\r\n=nTyD\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIB7O6pihz3SUEcPkavs/v15jKved/Bi1GWnN8wrz6fDDAiA+rgi3TJYP12kBcTXE0zHNMYP03bFzbPq91gDUS/zdYw=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.20_1631649794549_0.07708526526049475"},"_hasShrinkwrap":false},"0.0.0-preview-adde714":{"name":"@alphaflow/resource","version":"0.0.0-preview-adde714","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-adde714","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-iIkNCtEKC/wtECHcAoZCy7VEo5yR8wnLaELZpYdXJYxVyr59AZ6Xwj2D0DOCel2obJ1meOHab3PWgqPBDiPjkw==","shasum":"649d87b649b35a9ff68b513ee13fd9b0f668d32c","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-adde714.tgz","fileCount":6,"unpackedSize":224234,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQQ5iCRA9TVsSAnZWagAAMnEP+wev+NWI7KuiKXPgyopb\nOvzIsXVco0zIiT9ATCNHGPvXBQf+hNujg+pTASvjqIqs81SPs4hadvAI63oO\nHThMo8vSKR0ta8KnE0mx6l34sltWIv7uTvmblDoHy2WFhUS9uSY9i/rGe/8F\nF7+i569jyJqS8xg6BW+Crx70xqMsF93mNtGYGRWAdPqy+Yy2BmKohNm8zB5X\nxANDB6lBG5isaF7abuFISoQN8b8E3MphKPeRpvaplFAr9JZJG6uYsczuDJ8X\nxn0eip69LaUajkXhoApepQo04jFcwv9O5/n/h/l9h0nFzXjwJpg4wPMzPrMf\ngq7Xr9bkGVAPrCCVdzDdBhlH1EGu/aMcpdW//ayQFdOn75NGJWXvUf3IwWx0\nOVs4qUZD4Ne4Zc063Sm7oyuU/hkJ8B+VV6sPAINSr+hbA2T3ltHHr0ZwjbUc\nNcJakyiKpdybBUXNu5jijH+NWgm/Sh49jP6ghF5ousO0iqkqIFyazNfNcbAN\n6k2ISfKLRazucGVhf3ag9GrBHaOzRNmTyVHX4AoYQXHU44J4pHyuy+Wd8nF2\n2Sn+ib00+2Elj2J1v9ZoTUiXmu7wEu2abqamOQq4qWX7uV+b0b9Wz9zZmr3J\ng0/ZYPo0YBS3PujDktEW2uVTfPG3lkf+11WntSDPzBkKzuKqHtZeyofZ5gHq\nGfLv\r\n=YJoO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCZbqpBDbLdgKEIJQHL+ZQYx45zVQumOFf8+MqZ647qdAIgTXMFcOG20rF8+kFmuFFmL1QzRmYkBqf14hzM3ZDhg74="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-adde714_1631653474665_0.41970001423660586"},"_hasShrinkwrap":false},"0.0.0-preview-6702e25":{"name":"@alphaflow/resource","version":"0.0.0-preview-6702e25","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-6702e25","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-KtnjoDqs8d99b9pBuh/Mch4wYutvzKCQDZZ+uXvvTWHnc2XK9TR7EGAhD5LhB6cD/ZS8/TU8PDDnOhuS7/z5GA==","shasum":"19b16e2beb1ae70fce121df2cc7f132ee4b78910","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-6702e25.tgz","fileCount":6,"unpackedSize":224234,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQRE6CRA9TVsSAnZWagAAk4MP/A3/P5XEly4GXiDLc2jE\ntuXrzO7pbCYfAhXnTpVqmOXOawR20AAtTSG/fQV5oS6HqLdKsfkTCK5Eys0Z\ne36i9YHYfwZG17eYMGMCxH88MC2ikis6JfamKAkDhfE2+4PN+MsphtpASJ+g\nGZ2SwO/LhIYfan32Zru10InjbPd+3+5/VLSDCAv6B2AHzLEv32sf3r1k9xmK\nOp6C5nDglOrP8+5HLWYj5/1OmUe32re5te9r2TaxqWaTLMpbEuqxaOklySWq\n75m+Zzz84vTKmx2+SavMtoRgo2QnWxOF0sSDNVP/Im96qRlaS4zt/fqFd+Wb\n2GcgEPzmWCjjUz7YdfXqwTBQZrkD0oZytjxyD3YO3NCKjP18CKUEKrXDJc4l\nEU+AoMkYEsLXJaMLolXjuF0hB4YcNnVdYS+3bU7GkLfNxFZhsC3xkFPgE8Jj\n7VJ6FSfb3SMv5u6u7FeKnJ/dTfh7mpWmnPdjDs5mw428BCOlq+GZvlf2gd3S\nzU5q8/MUyhi296eVBZTUQFQ0ttVeTPiEWU6o99QVgaWeeoJFAGcRfGVc+4Rd\nTA33iJTm35zT/xbDhsZGtowUfE939FHauw7uE0QJePVETs8j1U4yAZqBgwYM\nX3jE+ppzXew1UNnD59GZyp4ZH9bT8rtAarYvzH8Ld+lIDiP4R6yt2P+Yt7ST\n7oKC\r\n=Tbai\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD2Eo/MnpblelrjvVy83cEXs7Ws6Gwf896EuBTyuu8FKgIhAK+QF22TyuxEyHF6JdhQ7Aw37SYWozHbjh8CSLaDgexv"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-6702e25_1631654202015_0.3005355911803893"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"1.0.0-alpha.21":{"name":"@alphaflow/resource","version":"1.0.0-alpha.21","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.21","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-T9gbhK0/NwiG93KZ2YzngzIjMfc2ZjAoHsfwjCkAUzb92jnd2BkR8+R7OCJ+Em2fzIdnou6zYslzRvwSgfOobQ==","shasum":"4407d8be6d1c7f9053658c8416ccd426e37d0e3c","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.21.tgz","fileCount":7,"unpackedSize":227125,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhQRP6CRA9TVsSAnZWagAA6ugP/jZiBkqHMS5LX6hZS0ta\nQTwhb+QdwU+0dowlCayC5aI3Z5LFpQih12Ytu0eoLwSs1q9cfvBs7VOnzpic\nKMcb1EZg7NHxc/vJx51N0iHgOLwZxCf1015vC9DPqUdqq1E2ilNSQEth295u\nrJjXuv3InwQeRhk95DE21iw4uJDmkf0KQrAuH4Wb+JePdzdabETVOoR0rRXe\nuqWuwYk76lSvEAlZbTLG2MQz3falFeWGdsNYCHLvvSlZws6A/B50xkojIC3e\nvEHgfMJ+Im4QESw4tZ6UcZXCF6AvHehRY2dEkhu+3+e1iS2axoMv8VjpD1+N\no+hcLfCRi/7fl/tQwk3HOuIQ/XgJbOMUA0YtfP6Triuai3+765j3YRdhbVX6\nN+WAT9OKqn+6fDXr8vQjXXdWN5NMI/ZTjWX0DVHrpiawjF95aNgVwqzEmHyg\ni49Xfs0M9AL4J9+4/8WJuYjzqRw6yUS+GCxqD6evQTb4Xb6parWu9SQRU/bI\nEhshyIk3bz2hGP2gGCcKoWlrZQHqq2supnUIIwq0USDxHpRczMllQ0O7bEUe\n4vu9g9S1Kcif6LO0Q9/4LPYrar8iC9GUTc6OD1Om+d8uMOKdnInOZ9yaJc91\n/zMZHV1mcUbwOGYiqYBOQDlAp8QpKsLPSJs1JnKoYsx8i9r8jT67soa3tBR4\nRk6e\r\n=a9GH\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICJl4xaIyL0X/FiVJQVeW0IOstXSaro77Qubg/VNYuM9AiAl9BOXTahLq5QaKnE+0+IgoDL2NkqJ+YjcZUpncWH5BA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.21_1631654906468_0.7754342187503911"},"_hasShrinkwrap":false},"0.0.0-preview-20646d4":{"name":"@alphaflow/resource","version":"0.0.0-preview-20646d4","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-20646d4","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-tzmvWLgy6s60DE9wvEOi9JN0LqG8BHyJhSHnhqA6WL9p5yunpHQOGsDaXJeFG2hRXp9cqCna1KuWMHJwUj9iWA==","shasum":"ef3eddcd99dbad057aae413ec6e8ea3036752bc6","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-20646d4.tgz","fileCount":6,"unpackedSize":224234,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFPLe81TtYSQqz5ctc9gjcHrZG7Ub3bURemSLb9d8liKAiEA6rAOOc0zH79fiWxlj5z3HcteTzZegg9EKKmDVATGQw8="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-20646d4_1631906654794_0.9717744032196951"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-865b716":{"name":"@alphaflow/resource","version":"0.0.0-preview-865b716","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-865b716","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-lQbHtP3o7RAL0CKrAFBQwAfputIx7a7iGnsIOxav2QnPSO4A7sz+rlspooh8D1eM9miMyU4fqM8JT7LPqPDKeA==","shasum":"de9b40f930f1b17e565b420ba8c91121bcaf8b83","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-865b716.tgz","fileCount":6,"unpackedSize":224234,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCID9DeWecd75ZwZUUKH7OogFdFVLTystmkVDDFfckYFG3AiEAwhjJYGO+TZhor3jZwSI6PjaMmazbN04gMr0Rw+1w8lE="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-865b716_1631908869682_0.9262118374527724"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-643fa0b":{"name":"@alphaflow/resource","version":"0.0.0-preview-643fa0b","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-643fa0b","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-3jPUlptX+9IeMOMEjglCpKRT0JXV4TPnHB6jsbiO8IjSwtAU2t5KlScGQijwpI1jorQbEj63tE6+MKUrZTcyyQ==","shasum":"089f2a1c0a2c0798387a543280ba302b5e1671f9","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-643fa0b.tgz","fileCount":6,"unpackedSize":224234,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCWsEt0FNQkoUHP3XI8MgJU/WlcZeLln9eE8xpT1xdWrgIgag7yO+EZfe1crBXX+WfESQf2nDAOm/GX+woKCrLMZEE="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-643fa0b_1631916822744_0.23481578135694114"},"_hasShrinkwrap":false},"0.0.0-preview-ed15b81":{"name":"@alphaflow/resource","version":"0.0.0-preview-ed15b81","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-ed15b81","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-HFMQN6JOn79om3LBKJdNmChijJhgKvwBVWe5P1FYIl9LQ5gmPDCn/W+zUI+7Ahlyr+Zj0TQHJmjEt/KZzWxjEQ==","shasum":"b723ee9e125fe45b6ff16816a8ea7c967aa0ed9d","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-ed15b81.tgz","fileCount":6,"unpackedSize":233214,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGcfCIU50z99S4ajDqOB4MClUDre4vIhA1atbSAt1e3iAiEA44DaS75rThIGe/6B2tPyrYwUiLEXyEQHB4rttVpnfus="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-ed15b81_1632179951751_0.4882612462413862"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-a79991c":{"name":"@alphaflow/resource","version":"0.0.0-preview-a79991c","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-a79991c","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-OVfLRMujm3W6SGVspr0A/mOZ0K4T9ozoTTcm0/oAQ6ZkYNJLC0WCy/93Z6a57Hypx876sbPnMwPtDp6btbI7Fw==","shasum":"eb5acff5c801f522798e6ad1037e127115b41094","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-a79991c.tgz","fileCount":6,"unpackedSize":232742,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIG2a5lkgfPLoYRgvtIlqfXb0TqnBID9qP17aKBSoEiqHAiEAwqBmIlCSIUguUORKuBl7AufrNB42DVFwgVK2b9KPjQk="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-a79991c_1632179970355_0.46681545981698136"},"_hasShrinkwrap":false},"1.0.0-alpha.22":{"name":"@alphaflow/resource","version":"1.0.0-alpha.22","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.22","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-71/0RLV1spnAtF74WzlKAjl1dQuQeFGSPtcw05hpTd5MHpg3IK89EgposCULk4lezzUperAGbjgPOTwb5Qn/vg==","shasum":"daec68b13b8550bf517ae8c3ce6eaf1c1a619b6b","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.22.tgz","fileCount":7,"unpackedSize":236218,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIB8Pe5U6cjQK9eJgR9imt1Yd6SBZfdMZJTW5qjnbPvCEAiEA+nHvXfk49Xc6pPrzC2HvSUXr5a5d1D1HbRHBnsU7L8A="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.22_1632187740204_0.7201844006237721"},"_hasShrinkwrap":false},"0.0.0-preview-e1c79a8":{"name":"@alphaflow/resource","version":"0.0.0-preview-e1c79a8","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-e1c79a8","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-fKGMXBNGNtG2zji+xUPfWzgt32OOp/s5zVWH8H9nlmvDl4lOSvwvw98loHWm9kFuTR971UpPtjByyG2LB/DKTw==","shasum":"9be60fd9a08bcabe938a12fee9f7c465a3b72b1d","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-e1c79a8.tgz","fileCount":158,"unpackedSize":263318,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDTXZ+aefRfuOD9Cs5+22bSdMdgPtsrIQCjlKrHOtFiBQIhAMOkwMSKHPFeM5/V1xWzowDgXx45gkknF16XVRT35/Fy"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-e1c79a8_1632410448997_0.5127165342503404"},"_hasShrinkwrap":false},"0.0.0-preview-3288ee4":{"name":"@alphaflow/resource","version":"0.0.0-preview-3288ee4","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-3288ee4","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-aD2H1PaBIz0CAHUm2ymP5jtV60cn/ijieawzRxx7N9xhhKjBAzp9MFDcr0Xt7508YNHhpxBnaitVVlNhw27x5Q==","shasum":"f556063fae28394cc3f041fbacf19714614ccd64","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-3288ee4.tgz","fileCount":158,"unpackedSize":263052,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCR8Shu7kDfNtrTYkHeJ8gNcRx/4qB0pI3rk3pJw05xEQIgBhyUeGO8lrVYcOAesRqjn4ufQLPRvsyHcJpPJcLwjzI="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-3288ee4_1632411620425_0.3426646025493518"},"_hasShrinkwrap":false},"0.0.0-preview-3028df2":{"name":"@alphaflow/resource","version":"0.0.0-preview-3028df2","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-3028df2","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-0mOsbshHTfJrwPra1MqLdrXiV+H4Gl3dKgcoyDCr+Lpgf60W6WMPSDtgOJtyfxS11dCH8Gs2VdJM9S+I2oKfwg==","shasum":"15894972b9dd6114c862b4b34672b918f596c6be","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-3028df2.tgz","fileCount":158,"unpackedSize":263052,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHF0Aly3T/ATnqyjDgkSRl/aEyJaOrs3q1ZsMpcskRbbAiEAvRi3cQmvbwkyJFs3JjwDMaDpP658UPTEsC6CWzFV+9o="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-3028df2_1632861850696_0.5321242312733989"},"_hasShrinkwrap":false},"0.0.0-preview-85a4345":{"name":"@alphaflow/resource","version":"0.0.0-preview-85a4345","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-85a4345","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-uo+UUzVL3nFPEWN9SE8nIPjbuMcHKgU3jHxLmCALXS2F/Xl0SuAXJYaaqmnO7rzd66sz81ku0YYYBOnCtm7Yog==","shasum":"8ca88ffbc8a061773f4d13ede67685312bf4e083","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-85a4345.tgz","fileCount":158,"unpackedSize":263052,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIC6gAEqcxLDGidu/YDcwOXmEFscM2s/ZaqfuMrNrHapRAiBVMHe2bgUG0SOFIIgZtzNCBDagXDoDM6B5UusXgcVAtQ=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-85a4345_1632861866459_0.2203420600381243"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-593cefd":{"name":"@alphaflow/resource","version":"0.0.0-preview-593cefd","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-593cefd","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-xCnEZMoysoBx6NeaZasQHbyxy76CHLlfIBq8RXsGHP+ItdfpavyWgwYngsGy2LPIq30QR4CPZeQtFVlRzH0Tug==","shasum":"d2ba3c7271de9e1859054236906d5f586d564050","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-593cefd.tgz","fileCount":158,"unpackedSize":263172,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDVdIjOf2ADqXUPtXZ5/+jxFkiuGHd2ZqBO+OfebPQSNAIgOwsBMwBXICnFUvhyN3l62PBcWKF0cmjdTO53qKpNth0="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-593cefd_1632862027806_0.744232972350527"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-c41e8ea":{"name":"@alphaflow/resource","version":"0.0.0-preview-c41e8ea","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","start":"yarn test --watch","test":"echo 'tests disabled'","checkPackage":"yarn package-check"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"11.0.1","@rollup/plugin-typescript":"8.2.1","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.52.8","rollup-plugin-terser":"7.0.2","ts-jest":"27.0.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-c41e8ea","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-Qb6vIezLYmIy1Niv/6Te+lqTWYe2Quqi8Yp0AoENn7rKHo2LR9MtyYWO++7ShbubzmVsNA0+dwr14rfQasthBw==","shasum":"1a915e158c59454c7a083e7cd5c88636dbbdc4cf","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-c41e8ea.tgz","fileCount":158,"unpackedSize":263172,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC5wTS5DFixrO/nQZUb2vQFcAhpr/rpgBfKaQiDnCtk7gIgexwEj4w8QRFlYxn97k7EIYfN/4Gg6E35PIVoCuH6jpk="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-c41e8ea_1632864377946_0.9684622503630902"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-616ac9d":{"name":"@alphaflow/resource","version":"0.0.0-preview-616ac9d","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-616ac9d","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-DHd+1wuOqnbV7bSxYwA2Ic3nLT4TSYYxsULhaQKWzNa7NpypqyB57Swbo6UQQsK07iyFith7IHN+sYA9kDPpRw==","shasum":"49fc7d3dd75686f4d8e18c280cac3dfe7b4449d8","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-616ac9d.tgz","fileCount":158,"unpackedSize":393493,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIA2VjDG5H6SozW5Eqd74k6XBRpFnacHDS1LH9JZds5U8AiA2+/vIQ7XeNJqGmBpJ7oo0be/ENVtGJv7iuTFSm11PzA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-616ac9d_1633015920329_0.34478611279718074"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-269dea8":{"name":"@alphaflow/resource","version":"0.0.0-preview-269dea8","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-269dea8","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-jrCTGPBagHFjmhWDclbxDnbdxoB/hTxacQ5cOCNPc/IiChrP2GZsNOuJNobdtJ8imK2FS7OYT29PAh1t+CM5yw==","shasum":"2b4847022b8c2c37cc1a85f14a6871697f779e92","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-269dea8.tgz","fileCount":158,"unpackedSize":393493,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD4VJgYO/a9LzVxjsuCim5mPoddvv61FOhXbeAK3X2z/gIhAN/PLu3eBAezJveyikhhDRIg4GxauKpPaN0C9h9jDCSe"}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-269dea8_1633018044437_0.27130584729818796"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-d91d500":{"name":"@alphaflow/resource","version":"0.0.0-preview-d91d500","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-d91d500","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-fLr2AesphJrRRif2IArZiiuX8KW2Ol3yJsbo5RTE5nbnOtbfrOnlI8PS4T6DOvpESi2DJSanJbuphDfGBnmeIQ==","shasum":"b56ae37ebc5e712e1ec073a2a84ea29a16bdcdf1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-d91d500.tgz","fileCount":158,"unpackedSize":393493,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD5Ur58IjSiKbeQEnc/iq3uyE78OjLxHLdUrlW3Qr39rwIgaz29QxY6toCfF7FHe0AoJUcLkUbrR9TWuq9wg+PiK7E="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-d91d500_1633039325945_0.3185741271149165"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"1.0.0-alpha.23":{"name":"@alphaflow/resource","version":"1.0.0-alpha.23","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.23","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-EA4tM9A2N3y54qpeMWvkjltbGoPVWsHdXTtMuBbEef9V6zCHjVBYtmsCeDLbVfyIlkld67P0UhmtSUhBqr5YwA==","shasum":"80bb2b1f3ed000a1506324963b2043674edc851f","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.23.tgz","fileCount":159,"unpackedSize":396540,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIB8Sote2HhffctXnGfEoJzL4cq3g3wx0q7t+/zVoFdCFAiEAhWZawaaEJrKExeatP25/megWuVWOQAYR+TMHlWxMX2U="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"markeissler","email":"mark@bunker5.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.23_1633040047222_0.9872864299760142"},"_hasShrinkwrap":false},"0.0.0-preview-36b8e7d":{"name":"@alphaflow/resource","version":"0.0.0-preview-36b8e7d","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-36b8e7d","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-tr04wDkEAWTuZH47DQI5NVfbZZu4I2dITnYuT2RdYvHN3OWqYu+WSKK47lNEb2JEVR6ZY/GbsVr+l/9OrTgdZA==","shasum":"fc2305ec7d1094c4c474833149e19fab0cb056e9","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-36b8e7d.tgz","fileCount":158,"unpackedSize":398412,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBAWWSfW7QTtIfwyzK3oyfDNDR9ugIiGpV7jS2ghcOb0AiEA7e8FyFtx/T+gPZ5c6etaKWg3K/SgbQnlC2dNPleov9Y="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-36b8e7d_1636393043295_0.9703335530296988"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-52149b3":{"name":"@alphaflow/resource","version":"0.0.0-preview-52149b3","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-52149b3","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-VLXDPLuqD76vBP+39yrvOcBowzSSxhyAX8JegGfJaUanjJWdNxXkjN2DKGoNUf5jlbHGPkj5xIg9OKgqllItsQ==","shasum":"58cdf5c0f433df228f16dca5fbc6375daba9b46d","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-52149b3.tgz","fileCount":158,"unpackedSize":398412,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIE7au2zmoQdECzOyWUhN6BFbXwkswRNK8cabKE1S4JzaAiEA7zYTZeJ1uIDKty09m5FvdpcQSrCtbzI7qyva5H33Kwc="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-52149b3_1636425257528_0.3456749321332899"},"_hasShrinkwrap":false,"deprecated":"preview publishes should not be used in production"},"0.0.0-preview-f15713a":{"name":"@alphaflow/resource","version":"0.0.0-preview-f15713a","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport { describeResource } from '@alphaflow/resource';\nimport toDoServices from 'src/services/ToDo';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport { describeMutation } from '@alphaflow/resource';\nimport toDoServices from 'services/ToDo';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from 'src/resources/ToDo';\nimport setToDoIsCheckedMutation from 'src/resources/setToDoIsCheckedMutation';\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n// called with a label and a function\n// the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n\n### Will do\n\nReduce code in reads/writes by supporting lodash.get style notation. Ensure call signatures are consistent for all of them.\n\nAdd more examples:\n\n- polling/sockets mutation pattern\n- error handling patterns\n- example of single resource update affecting others (i.e. ToDo affects ToDoList)\n- pagination pattern\n\nResource composition - we should be able to express that a ToDoList is an array of ToDo resources.\n\nPatterns for progressively filling out resources. Say there's some info I have from a ToDoList get which I can use in a ToDo get, how can I take advantage of that.\n\n---\n\nDepending on our experience with the library, the use return signature could be changed. This pattern...\n\n```jsx\nconst [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\nif (toDoFetchError) return; // ...\n\nif (toDo) return; // ...\n\nreturn 'Loading...';\n```\n\n...seems unsafe. If we're really really confident we won't end up in a state where we're not actually loading but say we are, that should be explained in the READMe.\n\n---\n\nAdd integration tests.\n\nFinish unit tests.\n\nMake sure we don't yield or refresh outside a mutation ever. That breaks the ordering of events.\n\nAdd react-style helper messages in development.\n\n---\n\nTake advantage of React Suspense.\n\nThe facebook team appears to be embracing an approach which involves thinking about code, data, and the ui in interrelated but analogous tree structures. This actually meshes nicely with this approach - where great concern is put on what the UI needs at a given time, and how that maps to cached data. We will need to figure out our own ways of supporting a render-as-you-fetch pattern, but this effort is a natural next step in this project, which can line up closely with where React is heading.\n\nAPI inspiration: https://relay.dev/docs/en/experimental/step-by-step#step-1-create-react-app.\n\nRender-as-you-fetch demo projects: https://github.com/gaearon/suspense-experimental-github-demo, https://github.com/relayjs/relay-examples/tree/master/issue-tracker.\n\n---\n\nImprove caching behavior. The current behavior (save what a user has requested) assumes that because a user _has_ requested something, they will use it again. This is working alright for us now, but it doesn't reflect a comprehensive understanding of or response to the topic.\n\nEven though at first blush, we might think every type of data fetched in any context has roughly the same needs, when you break it down there's a fair amount of variation:\n\n- **Enums**: we probably want these around indefinitely. They're lightweight and reused heavily throughout the UI. There's a case to be made for actually delivering them, pre-loaded, with each client because they change so infrequently.\n- **Loan lists**: these can be incredibly heavy in the worst cases, we only want to keep around the most recent search results. Because they can take a while to load, we probably want to keep that last search in memory so the user doesn't have to wait to re run it when they return to the loan list.\n- **Loan details**: we only really want this around while the user is on the page. An argument could be made for keeping the last details around in case the user wants to navigate back without waiting.\n- **Users**: at the moment, these are treated a lot like enums. But our user list will continue to grow, and at a certain point, it's probably best to consider them in the context of a UserList, which is only really going to get used in inputs. We will also want to fetch them piecemeal for comments, and I'm not sure if it's best to build up the cache based on users loaded through comments or to remove them when the component using them un-mounts.\n- **Comments**: in the case of the comment popup in the loan list, I think we'd want to keep those comments around and save the user the trouble of waiting for them to load if they mouse away and then over. That said, we don't want to keep those comments around when the user goes to the loan details page of an unrelated loan.\n\nThe React team also mentions pre-fetching data when a user hovers over a link. I'd like to adopt a behavior similar to this, but I want to express it in terms of what all of this \"means\" and stay away from heuristics. When a user hovers over a link, it does not mean they're going to navigate through it. It is a prerequisite to navigation, though. It's a challenge not to express this in a heuristic: when users are X actions away from data, pre-fetch it. For our use case, I don't think it's worth implementing a behavior like this for the marginal improvement in user experience if it introduces any amount of complexity into our business code.\n\nThere's some kind of overarching system or policy which captures all of this, but who knows what that is at the moment.\n\n---\n\nHandle get/re-get discrepancies.\n\nAn earlier version of the library never \"dumped\" data, eliminating the need to re-get a resource, and eliminating the possibility that user-defined mutation side effects would be overwritten. This really should not happen (and is unlikely to happen under a replace-only caching policy), but it's still possible, and would be really difficult to debug. Imagine sending out a mutation which sets the `updated_at` field on a resource. I might set this optimistically, but ignore the server response. Whenever I re-get that resource, I'm likely to get a slightly different timestamp back. This possibility undermines the predictability of this system.\n\nThis tight coupling and room for complexity can be dangerous. The solution here probably involves some level of de-coupling by checking the assumption that the effects of a mutation would match the results in any get operations on the resources they touch.\n\n---\n\nProvide warnings for cases where an identity is changing on every render, triggering an infinite loop.\n\nSet up some kind of invariant system like react has.\n\n```js\nif (process.env.NODE_ENV === 'production') {\n  module.exports = require('./cjs/react.production.min.js');\n} else {\n  module.exports = require('./cjs/react.development.js');\n}\n\n\n//\n\n  if (__DEV__) {\n    if (unstable_observedBits !== undefined) {\n      console.error(\n        'useContext() second argument is reserved for future ' +\n          'use in React. Passing it is not supported. ' +\n          'You passed: %s.%s',\n        unstable_observedBits,\n        typeof unstable_observedBits === 'number' && Array.isArray(arguments[2])\n          ? '\\n\\nDid you call array.map(useContext)? ' +\n            'Calling Hooks inside a loop is not supported. ' +\n            'Learn more at https://fb.me/rules-of-hooks'\n          : '',\n      );\n    }\n\n```\n\n---\n\nRe: UI before API: https://overreacted.io/what-are-the-react-team-principles/.\n\nThis library is kind of an extension of a thought experiment/a response to observed patterns. The end UI isn't always a concern in the way DX and conceptual consistency are. I wonder if there are ways to adjust this, and draw clearer lines between the UI and this library.\n\n---\n\nRe: Local reasoning: https://overreacted.io/what-are-the-react-team-principles/.\n\nNot super strong here. Changes to a resource usually require checking in on any possible mutation that might include them. I think react refactors are a great gold standard here - deleting things at a low level should have zero impact on things at a higher level. It's possible that the entire mutations system could be considered imperative, which should definitely be adjusted.\n\n> When something isn’t safe to do, we want the developer to discover the full effects of their change as early as possible.\n\n### Could do\n\nHandle button mashing. Inputs should be instantly responsive to the user, and what they see when they're done being dummies is what should be sent to the server.\n\nReturn a `revert` function from each yield. As long as mutations happen in order this should be safe.\n\nAdd a `useMutationState` hook which lets us see if a given mutation is active and with what arguments. Look out for cases where this might be useful (could be helpful in preventing button mashing).\n\nAdd the ability to freeze the store. Say we're in a text area and we don't want any socket/polling mutations to interfere.\n\nSwitch \"bail out\" argument for resource.use from `null` to `undefined`. Depends on what appears to be most comfortable.\n\nLeverage service workers and IndexedDB.\n\nQueue offline mutations.\n\nSwitch to TypeScript or flow for this project? It could make contributing a lot easier, there'd be less referencing inline documentation about how different stores should be shaped. Otherwise, validate store shape somehow.\n\nClear the store. Right now, data brought in is never dumped. This isn't great for tabs that stay open for months. If our apps are eager to open new windows, this is also less of a problem.\n\nDepending on our experience, it might be helpful to take more control of identities + managing how they look.\n\n### Won't do\n\n**Impose request timeouts.** While this library can improve DX by showing warning messages when the queue is locked up for a while, it is beyond the purview of the resources library to enforce limits on how long an operation should take. A mutation is an agnostic concept which _only_ describes a set of changes to the resource store, it should not have opinions about how long a set of requests can take.\n","readmeFilename":"README.md","_id":"@alphaflow/resource@0.0.0-preview-f15713a","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-OiKcnKSJVmSkgRGzixh/xrR9YAbOsAPshAG348Nzce8/FohpYhG96HXMqR+QdigRF4y6G7cov+NZvVcE8mvcBA==","shasum":"f5858ae69433b5e082d24b153ae9fae9e9c8894a","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-0.0.0-preview-f15713a.tgz","fileCount":158,"unpackedSize":398412,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICLtnZNJJFBion6t88Ud8BJC4XnewRKIc6yiXtVRCuagAiAL5P1p1Bs7zeWMu1i5MXFtE49MVgsHI9G/F9qZiwyhcA=="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_0.0.0-preview-f15713a_1636517049487_0.5506338476261559"},"_hasShrinkwrap":false},"1.0.0-alpha.24":{"name":"@alphaflow/resource","version":"1.0.0-alpha.24","description":"Resource management wrapper with a React hooks api.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"@alphaflow/util":"1.0.0-alpha.30","lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","jest":"27.0.6","matched":"4.0.0","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","ts-jest":"27.0.5"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"_id":"@alphaflow/resource@1.0.0-alpha.24","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-/lREKngt/RM7tm3DxkBhBmlH6AzMZ3HluPDDWbVcx5H9XdKwGtwsLan4yWnDkAsircry5ola46hV0oZoKODl4Q==","shasum":"32e6b075d858403364de621e87fe3efe39840866","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.24.tgz","fileCount":159,"unpackedSize":401546,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQChMAYu2K1J3h10GrrMEckX/nFmqsZ/R9PoSSDadwWJpAIgM5y3caTS/5xx14Itc72FydI7RFZ8/S41T9pTM5rJJq4="}]},"_npmUser":{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.24_1636675314760_0.1612318301994844"},"_hasShrinkwrap":false},"1.0.0-alpha.26":{"name":"@alphaflow/resource","version":"1.0.0-alpha.26","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"packages/*/package.json\"","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@scriptless/tinyapp":"1.0.18","@skypack/package-check":"0.2.2","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/lodash-es":"4.17.4","@types/react":"17.0.32","@typescript-eslint/eslint-plugin":"4.32.0","@typescript-eslint/parser":"4.32.0","eslint-config-react-app":"6.0.0","eslint-plugin-flowtype":"6.0.1","eslint-plugin-import":"2.24.2","eslint-plugin-jest":"24.4.0","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.25.1","eslint-plugin-react-hooks":"4.2.0","eslint-plugin-testing-library":"4.12.2","jest":"27.0.6","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.0.5","typescript":"4.4.3"},"peerDependencies":{"react":">=17.0.0","react-dom":">=17.0.0"},"gitHead":"3612d5ffc4e660dd08b83c06bc102ee88648b264","_id":"@alphaflow/resource@1.0.0-alpha.26","_nodeVersion":"14.17.1","_npmVersion":"6.14.13","dist":{"integrity":"sha512-eMHeLZNrph42Z6gMZAa0i6X3gK/myt//gy9wG2wVwg9KfUthhc5razPxtI5ay5+w1GdYzoo2IdzfW6Hkd9vPgg==","shasum":"ce45379e564f17bbd5ca2705b21273ebe1a278b8","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.26.tgz","fileCount":158,"unpackedSize":352829,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhq9icCRA9TVsSAnZWagAASnQP/0AUoCoqIgNLhCh7eYik\nVn1YUQkM+m3Q/rSfjxD1GDrgCm9gWuPO6sFosOw17m6rHD8Q/1RhpsnKKV5K\nIPoTpHkUyc/cpfNQdvQLikZD3sPRErkhZWCDEHNOdVz9LK72Q3x8iEEoUUw+\n01nVU+RvTN4CCBgXIcf4udr9fgaOG0OI+ks9oaznGMEIhxD5OQefx86twg2P\nzw6olrK/C4HvUg58R22xLdIOhSmFPDO5Kh1Rg/7sO/XSjNDc49c4yOde6O/p\nZwwS+8ujbzCIS2/LfSG2cagblSAS5zPFQnrg7HBqvoODXr8Ycljwg32uhP4n\n7nBJCS8L8Z2Ae5bcP5BiGTqH2+ugIgiXKnL/z8YielFAVUfc3ImEqjveNhyz\nlcW+/3rzcJrkFECkOZ52KiJ2s+SO/LIGH1CeTU4S27FtH9Oy9N6XcUYH/hD2\n9a3QoYk11pS1wuWxlt1reQePRE9YwgB3yWLeQro0jOlprQ6qz8RgP7QEWa/b\nazu21b99WbBFrrCb9BYS0gdsrLlsAdGh+fTDd6jTtOZIZ6O4IREdj+pajFpr\nqnUgFlzQPQ09Ap2XUAWY0aFPX3Z3gYM92q1ZIrqsv7T+tM+G6SdbU0FL1orF\n+hKGqeqIHlWRRd0Uv8zBOTN56MPmmQa2y0XOB2YFKXBatQmNIZhbVeumuoqK\nUjBS\r\n=BbNk\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEC+CqtJgVmNb0Y5Hwqv6Xrp+uRna7yOYWIAPslIXPvTAiEA1MxmTt+zXiYrSb0hKlma5nQc1D+UF6zjYxJ9zkZoCTk="}]},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.26_1638652060213_0.850780425164565"},"_hasShrinkwrap":false},"1.0.0-alpha.27":{"name":"@alphaflow/resource","version":"1.0.0-alpha.27","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"MIT","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4","use-sync-external-store":"1.0.0-rc.0"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/lodash-es":"4.17.4","@types/react":"17.0.32","@types/use-sync-external-store":"0.0.3","@typescript-eslint/eslint-plugin":"5.7.0","@typescript-eslint/parser":"5.7.0","eslint":"8.5.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.24.2","eslint-plugin-jest":"25.3.0","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.27.1","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"4.12.2","jest":"27.0.6","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.0.5","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"types":"./build/production/index.d.ts","gitHead":"38a9a5073e1633ef2d903d03043f83d496d856f7","_id":"@alphaflow/resource@1.0.0-alpha.27","_nodeVersion":"16.13.1","_npmVersion":"8.1.2","dist":{"integrity":"sha512-730BvsZfDqoeFjqKMlsvGcEfDTeUzBD4VMSjGn/Cb9q0nTMY2TbYLn4naeir6CTrWWhgc7d15Drjc5IoKDrXmQ==","shasum":"c82acb2ca893442b655796d3a2378a798974f4c4","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.27.tgz","fileCount":158,"unpackedSize":355530,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhzJqbCRA9TVsSAnZWagAAXRoP/2waTi7etL+TXPRcTORg\n7G9Yn8EGjGOyziYUsZZTZ1wJlwH2Kd6xRbDK7ryU4zgwsXmcXTdLmRqsAujV\n3fGX5FAMJ4TIdQx79STrk4djimSkFh8huS470/96d08r4W54TcInw75aox0+\nn7Zk9ChB9BDbiGgLf9lM7/Tw/t+Iu7kb36pZ+19Hi0+5MCVTfaTPJFLzCQLN\nOas2aOHXqbCcq8b2sUbCa35eLBWamSJFhd58D3Ps7T3Lv5ynzHMW5DGY+qWM\nyxPKcL2Cm2qN95Bxn+/9KXLJ8ZbO2VHQZk4wI55lGS7xSQ1nPprR0Enqnzue\nLr2OMZtDoUUszEjlttPEr4jtmgTbH5jahvZ9ahc0OeLha4Ma11fbdlEmsiPK\nQrKhG8yyI7KxWuFTUZktpsPERCHbl5/Hut8A5Wi+/6pBRiVvk2RZTiLSOt/7\nWqUL3/fNgAZ7wEvNE3LabgWW5d4twRlq85uYXWk+QMvMNqhE0AbwBRlcYTBl\npXjWj0WjvvJMp5J8YQBGHEKf36XDdrC0q0gCzTbOIZk1coNuFYFfb/8Xp7xu\n1RtXXSR3iqOKv1KzwK4qPVappvgUWpL2RNdUJAoMCiUAXpE6w2KQTuWoK20d\nMp248Dv0GbQ3ngPJBtelijjxnI/palbJVA8r34BgauM6czFYu2TVwhGKqQht\nSD5N\r\n=QIDX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDtqtiNtPv+J+bypnmI75+nT/tyr5S7zb3/nbqI+e7MVQIgVk7BnvdlNUWI9dMjEtoE++PTuGq45FQjZfjSXYOOSFg="}]},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.27_1640798875325_0.38369952586636824"},"_hasShrinkwrap":false,"deprecated":"bad publish"},"1.0.0-alpha.28":{"name":"@alphaflow/resource","version":"1.0.0-alpha.28","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/lodash-es":"4.17.4","@types/react":"17.0.32","@typescript-eslint/eslint-plugin":"5.13.0","@typescript-eslint/parser":"5.13.0","eslint":"8.10.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.25.4","eslint-plugin-jest":"26.1.1","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.29.3","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"5.0.6","jest":"27.5.1","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.1.3","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"gitHead":"92a2b8272728833e3d45c0682cdddb362b34845d","_id":"@alphaflow/resource@1.0.0-alpha.28","_nodeVersion":"14.18.2","_npmVersion":"6.14.15","dist":{"integrity":"sha512-6IZ4/Vy7P7LCBf9v5w/TPumjGj3ZHLyAmY+O2L8CDXLi31KEd7TlXe2hwGloukvpgUyotTPUU3ignpr5mI3JLA==","shasum":"b135ee8cfd80840784b98970fd49969338f3b802","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.28.tgz","fileCount":158,"unpackedSize":353654,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiImPNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqhRQ/8CKBtS96sDcbyMv4zn8EGjBiCOScFdXi+ba68rihQSfQyJnx0\r\nv5VrLnbrG8xb2dKOQ98DbGriF3mlFmFkJ7Zz8JkcG4PLdVMrltuSILkdyzvZ\r\n4u3OTjF+od2YBIvHlsRsMWIYOPcIiLaobv7EQWZPa/3jC3rIe+pFDuBHEJuG\r\nbcKmGi+e282byZfor363SsVxp6oAgnk3JgCj0GZDuV78HRYyyK686g/iYMIe\r\neGveQyrdL7AyeFEWWa89bxnawtCszqFyt096Atv+SnGXmePG0EgVLNXpYTrm\r\n7hRBHXKcSq7i9Nc5AinlhyuB5HI6jR9gcXrsLUfhpEBqz0eHSz5CQEBMM4I8\r\nnKXjvwNTloN9gujjVPBeXRerxsxUFZOtkYtyKHYw51XGd9rjpzp5NyQ7PcVJ\r\nErsny7OOhuZ5/fen2qss5sFS4tq6LraH3DYQBuvrj4orKdi5uht4u2L7urqp\r\n3iQU2DSft+E/apX6Tm56WWWX9WMB2uLMnFe2R4XQkEMl6j7vbeYethbuye3S\r\nYuc6jbz4hAH4IEGNXgQWGB6Y81ZfITfRUGeP862ucODTWrSJgfOsTFCLoSI7\r\nh57VdZTrgM8OO9wD1u4FM04Gxs1wqq/kvL1ynrvUQG1Phw93b+OFprcwVXj2\r\nGwomXXwBzwHswtAdvvpWPqO11rfkgx3Rck8=\r\n=ONrP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIACCwlQEoOrbkQD+MWs/Fn9algbrwoCPljYPqRgang/MAiEAwI5xx8EK6xXfjR+2XYNNBCgSHqBTbCRU9q+/Ec2KhIk="}]},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"gabriel_rinaldi","email":"gabe@alphaflow.com"},{"name":"joshayres","email":"josh.ayres@alphaflow.com"},{"name":"peter.christie","email":"peter.christie@alphaflow.com"},{"name":"wluk.alphaflow","email":"wallace.luk@alphaflow.com"},{"name":"rakshith.venkatachalapathy","email":"rakshith.venkatachalapathy@alphaflow.com"},{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.28_1646420941330_0.5990135209524785"},"_hasShrinkwrap":false},"1.0.0-alpha.29":{"name":"@alphaflow/resource","version":"1.0.0-alpha.29","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"echo 'tests disabled'"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/lodash-es":"4.17.4","@types/react":"17.0.32","@typescript-eslint/eslint-plugin":"5.13.0","@typescript-eslint/parser":"5.13.0","eslint":"8.10.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.25.4","eslint-plugin-jest":"26.1.1","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.29.3","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"5.0.6","jest":"27.5.1","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.1.3","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"types":"./build/production/index.d.ts","gitHead":"8f556235399e9b400e87ade5fe05e1625512eaf6","_id":"@alphaflow/resource@1.0.0-alpha.29","_nodeVersion":"16.14.0","_npmVersion":"8.5.2","dist":{"integrity":"sha512-YguoTo60lhWyfHPZunZiyioznbIH7+JUap2OrBwkt0oMxF97A8A81NzIb1fMCta22YdWKFxrZSN56ndeO/wpgQ==","shasum":"42a77d6f79aa8d8361252f54197736cb91004579","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.29.tgz","fileCount":158,"unpackedSize":353654,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCenK6+pT2fPCxFg8MATW0Dk2mJbcsrdMXk7D01VgesEwIgW3R3ki+opRnzZwey/gVjED3Kq/UPrc9KA+iDPjUI+14="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiSM7jACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoV2Q//YP26hP2P6m7EFdMUhHuJo97nHSQxkkuhX6pTZV+5YMLvBEyL\r\nbddQo3wMC/3v46BSmsHT5yh03iUQ1KrkAzGi9zdzwcmL8TbTnwTkQrgkJz59\r\npQCC2vv9LVdYPyBhXrz3Bvnzy//2ErCtGCmNnwnAypLHYWTbhrvGuDAFEFxO\r\ncaBI+jQ1FB4C65c/q+DVWeaFUJfvWkfITUwF8kT5khTtWmdeBHdPBI0lZUCr\r\nNTso02i589yuyDVxaRCdJmHA/M22hGJfpOjS1+KuGIZAp9VCqvcbf/8JcvQe\r\nDzlTv2oVlYxXhEX8SwM8rEEE9/CXn+u7T7VHkf2CjKJfqv0yocJYpu35c0Y9\r\n+c0nnC0gZxEh+R3CvPB9Ylm+lYYNSKHOC1SEYqkGQrPOV6t6347jC0KC9vxU\r\nN4zIngU3PTiOJbEd1wvq5NrF98VKk+5bFQbqKEEZevevScjVuVN6HSscNaIB\r\nX+kUjLmbacQXFvdLLc/7+OkHVe4x11GOg3p7/wQXNSoPMC11aJH9q0ysun/A\r\nn2TpYdVX5Kayx1u5GtNXTG96M2VTjPRT7RYGrnYJiK58kgEMUcTl+Nws0a9Q\r\nUSfLorsdaKLhwSk8Z8YsXf2zIZ5qa7wywU6yLtQlouKz+iBpHfx7kX/CvSDM\r\nLI50YRJMeoq556qTuYsdVO0Ml4SLygq3KFQ=\r\n=SWeD\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"znahapetyan_alphaflow","email":"zakar.nahapetyan@alphaflow.com"},{"name":"gabriel_rinaldi","email":"gabe@alphaflow.com"},{"name":"joshayres","email":"josh.ayres@alphaflow.com"},{"name":"peter.christie","email":"peter.christie@alphaflow.com"},{"name":"wluk.alphaflow","email":"wallace.luk@alphaflow.com"},{"name":"rakshith.venkatachalapathy","email":"rakshith.venkatachalapathy@alphaflow.com"},{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.29_1648938723107_0.5996384287219081"},"_hasShrinkwrap":false},"1.0.0-alpha.30":{"name":"@alphaflow/resource","version":"1.0.0-alpha.30","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"NODE_OPTIONS=--experimental-vm-modules NODE_ENV=\"test\" jest --no-watchman"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@testing-library/jest-dom":"5.16.4","@testing-library/react":"13.0.0","@testing-library/react-hooks":"8.0.0","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/jest":"27.4.1","@types/lodash-es":"4.17.4","@types/react":"17.0.43","@types/react-dom":"17.0.14","@typescript-eslint/eslint-plugin":"5.13.0","@typescript-eslint/parser":"5.13.0","eslint":"8.10.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.25.4","eslint-plugin-jest":"26.1.1","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.29.3","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"5.0.6","jest":"27.5.1","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.1.4","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"gitHead":"6f7f006da8c579a201e5ac2827ece7766dc948d7","_id":"@alphaflow/resource@1.0.0-alpha.30","_nodeVersion":"16.14.0","_npmVersion":"8.5.2","dist":{"integrity":"sha512-fy8fIuT/WyYJLqrx6uw98xYa1OnbxU9EL+cNDtkt+0DjvFWKCHvo/hB1tcN+tAdRjWkEAKeJDUiySQn4fOR/4g==","shasum":"777e74d98a128020eaebb3d3907b31b92ada3100","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.30.tgz","fileCount":80,"unpackedSize":207425,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDZTxfVRNXLr44MrjJ6AeKcPHXUJBTF84JUgTjJzo4JpAiEAkBDzpE6Q5CKAMaSI7C/AOYmMM3MPneZfvs++tShcGMo="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiVb3YACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqFPxAAh5PiidN1JrgqaCxtSj6nUeb/MhR0iUlUd+npXrd+tbGQsZ1X\r\nkNoY9mCRVO3oMrD87SkGqQ+8FW3ndWdTsADq2dnRqQVXi2e4M/Jf+Exolet4\r\nkzI18H8cUqUrjAIeArVe/6ttoAnZY6D4Hy9NU0kJNBXEIfSR0GFBpeqyYfWC\r\nogRu3X0ZJxJt3KJ7A2Z1XK/JxAQAVBh2Wx+fn41ojMgSRMBACM3XGVfGvFtT\r\n5vNN4bXcRWYIw+1p7xz2H8aUTYUlagIA0/ztr7KMD0e2KHWPke0Q173s1eYP\r\noYyJKzEAzVP45Jk8dT1TGl0KPvIzEMt56vkkEJrNrovqb1jFErlaWd8MTacQ\r\nm9PQ4kvhVGA+q1fo4YrV7wC9hXv+8AwVlSoaXefyTqJAJOZtjl+9TNcS+yX8\r\nFMJ2vmsH/FIlcD67JzzLi0/SE6GxKKksDrdLONWFi79h3V5X/g5Qi09CBsO4\r\nUo2gHBUXdEJ8e3CPQKIeeGNpGfT93lsgQljrYmju/C7xJ8fYoh4y7yII/9/q\r\npzqP8mTXwgwwA9Rhwbh6E0C1fPuFKGr9v5Gfk8NoQhhd0pVayRnlJWdxV6tn\r\n342JQJONr5T+YA2zIddMY3f1FbYT+i5nGMqUQ30ufrXXpJ0bKMBYjzqhE8dk\r\nh4LNKpZNp6DY4zpfcUoy9q/FN/OW4SJTmFg=\r\n=QJjS\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"znahapetyan_alphaflow","email":"zakar.nahapetyan@alphaflow.com"},{"name":"gabriel_rinaldi","email":"gabe@alphaflow.com"},{"name":"joshayres","email":"josh.ayres@alphaflow.com"},{"name":"peter.christie","email":"peter.christie@alphaflow.com"},{"name":"wluk.alphaflow","email":"wallace.luk@alphaflow.com"},{"name":"rakshith.venkatachalapathy","email":"rakshith.venkatachalapathy@alphaflow.com"},{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.30_1649786327996_0.7337937763611819"},"_hasShrinkwrap":false,"deprecated":"bad build"},"1.0.0-alpha.31":{"name":"@alphaflow/resource","version":"1.0.0-alpha.31","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","types":"./build/production/index.d.ts","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"NODE_OPTIONS=--experimental-vm-modules NODE_ENV=\"test\" jest --no-watchman"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@testing-library/jest-dom":"5.16.4","@testing-library/react":"13.0.0","@testing-library/react-hooks":"8.0.0","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/jest":"27.4.1","@types/lodash-es":"4.17.4","@types/react":"17.0.43","@types/react-dom":"17.0.14","@typescript-eslint/eslint-plugin":"5.13.0","@typescript-eslint/parser":"5.13.0","eslint":"8.10.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.25.4","eslint-plugin-jest":"26.1.1","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.29.3","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"5.0.6","jest":"27.5.1","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.1.4","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"gitHead":"b0f2655e28e420df441d5304646c7a8e02641abd","_id":"@alphaflow/resource@1.0.0-alpha.31","_nodeVersion":"16.14.0","_npmVersion":"8.5.2","dist":{"integrity":"sha512-3W8Wyda9B6QCAi+P44uWzlP0HiXHAcYUqdwSimpuT08kq+bAUtTb6Q5qIk39XEuVTbm444fg42tfXOvx8ZH1tA==","shasum":"a920c2ce69161daf8a6de8ed04bad216e9ee24a1","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.31.tgz","fileCount":158,"unpackedSize":358734,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC+hDK0X5vxhHUOq/jbdtMo6odN1vsdlS6jMQp8AtIq4wIgfcTsZbychEID6Tbjg4E73BIuWbZfZaqznDyXSdiC6cw="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJieFhAACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmokPA//Vyn0pPavOfgj7xnBk2cn5+aNHwI+qvwtXXSQcGhtLjx15o9g\r\nC6IvAl2FQXqPPQramELx2TSSytckEI8Kh0Er10LElEFSKU6J1huoifPRY0UY\r\n+Vf6SN2De2LplnhYkZenNCikiML1A/Ya89AnGwItWtlUMbBzWMpw2CxUwMRw\r\njQku/DtokcqsAlxaJYRJi+fbNBBSzFFMxY9rtxiErbRXfNeo+jIEdEbWK26I\r\ndhx3i9QDthbUmaaQBTt1IU4cOeHGWPhFyT7a7s9mbJZPUEgPjqasIDcvN6lw\r\n+PhXQ+PL1ACl+kHQ5Xa+S1YUHAepZwFzTREBNak1GwH92YYGZPUACoE91RHo\r\njnt17UMGqkGT7X7tgqAtfOctfC4+DYSfuc4sJxr+0Sn8CUNG0wDS8j4lJlsk\r\nFVZ3rL2d4Rq867ubJ+/6tKiOPTWNoL46Mb+81MlbnuUtG4L306bCgnHcXqqP\r\nQKyw2UKuQAcWQErzjqyCdkMh5oF5/nJpa/meUCbBT6SDQThaEJZZrLq3M9+M\r\nwB4Tj9Y+56A4649bY+yF46Ya6RCRnZDFSstozfTmwgZUz07n9Evu/UE4fBPb\r\naxpmsMHTkW286Gth+sLaqBTomIOR3rI19Zm6BLX436fOoyv4KGh2qBQNP0Nj\r\nQPDv7g3fYB069HrpTrpUQicil4K1MDwbNzs=\r\n=yrjD\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"chris.liddell","email":"christopher.liddell@alphaflow.com"},{"name":"znahapetyan_alphaflow","email":"zakar.nahapetyan@alphaflow.com"},{"name":"gabriel_rinaldi","email":"gabe@alphaflow.com"},{"name":"joshayres","email":"josh.ayres@alphaflow.com"},{"name":"peter.christie","email":"peter.christie@alphaflow.com"},{"name":"wluk.alphaflow","email":"wallace.luk@alphaflow.com"},{"name":"rakshith.venkatachalapathy","email":"rakshith.venkatachalapathy@alphaflow.com"},{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"james.darby@alphaflow.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.31_1652054079823_0.05588058270141927"},"_hasShrinkwrap":false},"1.0.0-alpha.32":{"name":"@alphaflow/resource","version":"1.0.0-alpha.32","description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"license":"UNLICENSED","type":"module","types":"./build/production/index.d.ts","exports":{"development":"./build/development/index.js","production":"./build/production/index.js","default":"./build/production/index.js"},"main":"./build/production/index.js","scripts":{"build":"rm -rf build && rollup -c","buildAndWatch":"rm -rf build && rollup -c -w","checkPackage":"yarn package-check","format":"prettier --write \"./**/*.{js,jsx,json,ts,tsx}\" && sort-package-json \"package.json\" \"./*/package.json\"","start":"yarn test --watch","test":"NODE_OPTIONS=--experimental-vm-modules NODE_ENV=\"test\" jest --no-watchman"},"dependencies":{"lodash-es":"4.17.21","redux":"4.0.4"},"devDependencies":{"@rollup/plugin-eslint":"8.0.1","@rollup/plugin-node-resolve":"13.0.5","@skypack/package-check":"0.2.2","@testing-library/jest-dom":"5.16.4","@testing-library/react":"13.0.0","@testing-library/react-hooks":"8.0.0","@trivago/prettier-plugin-sort-imports":"2.0.4","@types/jest":"27.4.1","@types/lodash-es":"4.17.4","@types/react":"17.0.43","@types/react-dom":"17.0.14","@typescript-eslint/eslint-plugin":"5.13.0","@typescript-eslint/parser":"5.13.0","eslint":"8.10.0","eslint-config-react-app":"7.0.0","eslint-plugin-flowtype":"8.0.3","eslint-plugin-import":"2.25.4","eslint-plugin-jest":"26.1.1","eslint-plugin-jsx-a11y":"6.5.1","eslint-plugin-react":"7.29.3","eslint-plugin-react-hooks":"4.3.0","eslint-plugin-testing-library":"5.0.6","jest":"27.5.1","matched":"4.0.0","prettier":"2.4.1","react":"17.0.2","react-dom":"17.0.2","rollup":"2.57.0","rollup-plugin-terser":"7.0.2","rollup-plugin-typescript2":"0.30.0","sort-package-json":"1.52.0","ts-jest":"27.1.4","typescript":"4.4.3"},"peerDependencies":{"react":">=16.8.0","react-dom":">=16.8.0"},"gitHead":"103a0b41b55e3682cec89cd2bae0a9963777325b","_id":"@alphaflow/resource@1.0.0-alpha.32","_nodeVersion":"16.14.0","_npmVersion":"8.5.2","dist":{"integrity":"sha512-QbDN1yzU9wrWPKunpqK9Dj2qe3DA/3EBA0YPGs6izzv4MRBtvzzaWG42iKnZ5Zcv2NakxY7MrFhWR5yMDUgiyw==","shasum":"8ac93ba409dcabfb631e6bee2705e654b8d1b7f2","tarball":"https://registry.npmjs.org/@alphaflow/resource/-/resource-1.0.0-alpha.32.tgz","fileCount":158,"unpackedSize":359017,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC4O99T3HoaAPwGZ559oBnlIfOB/Ab6xcC0zUXSmgH6wgIgZycWuD+cf431CigKcMuubKcUjXTj19dJoBmU44Fyqpg="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJijaHlACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmphIA/+Iqh/9XCSgc7QhIzZytRAF4wngC0cZ4Gwi688OHIorwAtEtov\r\nlUagVD98MdOarDaomB9ZyfOo1CxlsHs7No7dBE1QEnuXyLjcL8qFcas5vzxj\r\nMLgcpvYTGzeA4OWuLpKb3FjnUesbdijtd0PKJk7isab106OnDD8gNT0KHhQg\r\nOTt2TgpEm0evCSS43kM8kE+tTzif5XIOOQjon9Hq8GJZKvjdHKeoYL+/SjFn\r\nPgNdZm2caWUKu/q+Yuuimv+W2SJiSUO+Z6s4cmy9+0d/JwuKZsCcBQV72gPb\r\ngs7MEAdgC1wKEY55F2+MD5Umecc5UHVuUoUUb/rrgyQmIrU4+PH3pQXW2lVU\r\ne6ilqWOL2nYRrwioctQ/4PKdN1+eWIOQpboR3yEN03Gtjt/ELDjldINrXD5Z\r\n4AlsfX3M32kewb6LjquQOFjIaKTNn7UllMCjPHHWwZYcWygEV+DGdUJFJW3i\r\n1qnoIiNE7tkN3OpHU5pfkLN/5tMsYHsxzxUltb/9DKWRBSATD1lsB0k8rT6b\r\nvLCQu2CCN7H+0fHKjZ3iRLEQ1x1+kOwN2Of3D75V4sbPcW6Vqtgg1VBundCl\r\nZ32S492FLz+8v4Ci69xOHOc/XZ9L7MdkaAZf38tmQsfr1KQmHNtOzFr1GvgB\r\nUtIz6bUrIsfQ1eEAE5xrn4Qz22tJtwThWXE=\r\n=g+HG\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},"directories":{},"maintainers":[{"name":"chris.liddell","email":"christopher.liddell@alphaflow.com"},{"name":"znahapetyan_alphaflow","email":"zakar.nahapetyan@alphaflow.com"},{"name":"gabriel_rinaldi","email":"gabe@alphaflow.com"},{"name":"joshayres","email":"josh.ayres@alphaflow.com"},{"name":"peter.christie","email":"peter.christie@alphaflow.com"},{"name":"wluk.alphaflow","email":"wallace.luk@alphaflow.com"},{"name":"rakshith.venkatachalapathy","email":"rakshith.venkatachalapathy@alphaflow.com"},{"name":"hannahmarie","email":"hannah.ponce@alphaflow.com"},{"name":"nscharfe","email":"nscharfe@gmail.com"},{"name":"alphaflow-engineering","email":"engineering-admin@alphaflow.com"},{"name":"gnordhielm","email":"gus.nordhielm@gmail.com"},{"name":"jwonsever","email":"jwonsever@gmail.com"},{"name":"mike591","email":"michael.mach@alphaflow.com"},{"name":"cchamplin","email":"caleb.champlin@alphaflow.com"},{"name":"emilmirzaian","email":"emil.mirzaian@alphaflow.com"},{"name":"james-julius","email":"jamesdarby7@gmail.com"},{"name":"katewood","email":"kate.wood@alphaflow.com"},{"name":"tony-o","email":"tony.odell@alphaflow.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/resource_1.0.0-alpha.32_1653449188987_0.586682075527724"},"_hasShrinkwrap":false}},"time":{"created":"2019-10-17T00:58:55.042Z","1.0.0-alpha.0":"2019-10-17T00:58:55.145Z","modified":"2022-08-11T17:01:53.093Z","1.0.0-alpha.1":"2019-10-23T17:50:36.400Z","1.0.0-alpha.2":"2019-10-23T19:20:29.894Z","1.0.0-alpha.3":"2019-12-04T04:56:07.076Z","1.0.0-alpha.4":"2019-12-05T18:52:35.236Z","1.0.0-alpha.5":"2020-01-03T01:32:17.847Z","1.0.0-alpha.6":"2020-01-17T04:18:34.688Z","0.0.0-preview-38023c7":"2020-04-20T22:34:05.186Z","0.0.0-preview-e19df32":"2020-07-15T19:57:47.514Z","0.0.0-preview-bcea939":"2020-07-15T19:58:02.125Z","0.0.0-preview-1c1c3f8":"2020-07-15T20:01:45.419Z","0.0.0-preview-d0f4ca2":"2020-07-16T19:21:36.161Z","1.0.0-alpha.7":"2020-07-16T19:28:07.224Z","0.0.0-preview-2bc7839":"2020-07-16T19:31:05.729Z","0.0.0-preview-8013cdd":"2020-07-30T21:29:28.010Z","0.0.0-preview-dcc5ba4":"2020-07-30T21:31:07.519Z","0.0.0-preview-4c7266f":"2020-10-27T19:38:48.625Z","0.0.0-preview-d7f427c":"2020-10-27T19:39:12.047Z","0.0.0-preview-bb06dde":"2020-10-28T16:33:50.957Z","0.0.0-preview-22fa402":"2020-10-28T17:07:41.087Z","0.0.0-preview-d6fe26d":"2020-10-28T17:18:00.424Z","0.0.0-preview-db15faf":"2020-10-28T17:30:38.816Z","0.0.0-preview-b22cbb0":"2020-10-28T18:27:23.347Z","1.0.0-alpha.8":"2020-10-28T21:12:04.776Z","0.0.0-preview-b8e5b64":"2020-12-09T23:03:47.451Z","0.0.0-preview-67bcca6":"2020-12-11T07:16:23.615Z","0.0.0-preview-05ca6da":"2020-12-11T19:24:50.998Z","0.0.0-preview-2c74b10":"2020-12-14T06:25:04.373Z","0.0.0-preview-60cd744":"2020-12-14T21:12:14.947Z","0.0.0-preview-32c0141":"2020-12-14T21:14:14.913Z","1.0.0-alpha.9":"2020-12-14T21:16:12.377Z","0.0.0-preview-85ece4f":"2020-12-15T20:02:22.578Z","0.0.0-preview-aa86790":"2020-12-16T05:00:41.819Z","0.0.0-preview-a874486":"2020-12-16T22:27:57.654Z","0.0.0-preview-0bdc7c2":"2020-12-17T00:33:20.535Z","0.0.0-preview-f4750b9":"2020-12-17T01:01:36.057Z","0.0.0-preview-677a688":"2020-12-17T21:36:50.412Z","1.0.0-alpha.10":"2020-12-17T21:41:14.032Z","0.0.0-preview-a045f65":"2020-12-23T02:25:01.908Z","0.0.0-preview-0101a85":"2020-12-23T19:31:19.305Z","0.0.0-preview-f958760":"2021-01-04T18:09:22.169Z","0.0.0-preview-7bb355d":"2021-01-04T18:39:53.495Z","1.0.0-alpha.11":"2021-01-04T19:02:10.324Z","0.0.0-preview-32bb792":"2021-01-05T01:13:44.265Z","0.0.0-preview-f911b1d":"2021-01-05T01:43:34.820Z","0.0.0-preview-9b164a1":"2021-01-05T17:32:32.437Z","0.0.0-preview-4991854":"2021-01-05T18:25:43.854Z","1.0.0-alpha.12":"2021-01-05T18:42:34.401Z","0.0.0-preview-9f93c13":"2021-01-05T23:37:07.308Z","0.0.0-preview-3a23629":"2021-01-07T01:09:57.227Z","0.0.0-preview-3642bcc":"2021-01-12T01:25:15.953Z","0.0.0-preview-1e588bf":"2021-01-12T06:17:39.143Z","1.0.0-alpha.13":"2021-01-12T21:50:54.038Z","0.0.0-preview-15a4baa":"2021-01-22T02:00:54.122Z","1.0.0-alpha.14":"2021-01-22T21:39:26.649Z","0.0.0-preview-0a42039":"2021-03-11T07:02:52.100Z","0.0.0-preview-8ae0780":"2021-03-22T21:57:20.800Z","0.0.0-preview-27298c2":"2021-03-23T02:10:03.242Z","0.0.0-preview-b399dba":"2021-03-23T02:55:39.878Z","0.0.0-preview-4024b97":"2021-03-24T03:08:53.996Z","1.0.0-alpha.15":"2021-03-24T05:13:48.337Z","0.0.0-preview-88c965f":"2021-05-05T18:07:19.671Z","0.0.0-preview-ed04cea":"2021-05-05T18:12:00.602Z","0.0.0-preview-ebfed6c":"2021-05-05T19:58:47.378Z","1.0.0-alpha.16":"2021-05-05T20:12:16.965Z","0.0.0-preview-5b61af9":"2021-05-11T20:00:33.994Z","0.0.0-preview-522e923":"2021-05-11T20:13:00.365Z","0.0.0-preview-d8e9c9d":"2021-05-11T20:22:32.960Z","0.0.0-preview-fc1d929":"2021-05-12T03:12:48.030Z","0.0.0-preview-e4810b2":"2021-05-12T04:24:39.804Z","0.0.0-preview-b81878d":"2021-05-12T16:18:08.380Z","1.0.0-alpha.17":"2021-05-12T18:56:43.827Z","0.0.0-preview-d477d4f":"2021-05-14T21:31:44.958Z","0.0.0-preview-6838b0d":"2021-05-17T18:21:14.828Z","0.0.0-preview-fe51732":"2021-06-14T19:21:33.943Z","0.0.0-preview-4de4a9a":"2021-06-23T22:51:55.417Z","0.0.0-preview-83938d3":"2021-07-02T19:48:29.357Z","0.0.0-preview-b922b57":"2021-07-02T19:50:35.855Z","0.0.0-preview-975e136":"2021-07-06T17:17:05.923Z","0.0.0-preview-5e7fdcd":"2021-07-06T22:03:43.926Z","0.0.0-preview-e027c3e":"2021-07-07T17:23:54.461Z","0.0.0-preview-9929f7f":"2021-07-07T17:37:12.282Z","0.0.0-preview-e692012":"2021-07-07T19:47:46.553Z","0.0.0-preview-0f2bdcc":"2021-07-07T21:05:11.363Z","0.0.0-preview-694d996":"2021-07-07T22:11:27.270Z","1.0.0-alpha.18":"2021-07-07T23:10:19.609Z","0.0.0-preview-d9f8579":"2021-07-12T21:57:46.776Z","0.0.0-preview-2f100ff":"2021-07-12T22:10:21.391Z","0.0.0-preview-216a55a":"2021-07-14T03:10:03.313Z","0.0.0-preview-95a03c7":"2021-07-14T03:24:33.955Z","0.0.0-preview-57357f8":"2021-07-14T21:40:49.019Z","0.0.0-preview-348f929":"2021-07-14T22:54:18.956Z","0.0.0-preview-6ccf9e2":"2021-07-14T23:14:09.746Z","0.0.0-preview-204f8fb":"2021-07-15T00:12:45.749Z","0.0.0-preview-6067391":"2021-07-15T00:13:33.741Z","0.0.0-preview-83ed5ee":"2021-07-15T00:26:13.169Z","0.0.0-preview-0a5629d":"2021-07-15T00:47:40.708Z","0.0.0-preview-6fb5918":"2021-07-15T02:14:33.712Z","0.0.0-preview-41149a7":"2021-07-15T17:58:55.000Z","1.0.0-alpha.19":"2021-07-15T19:24:10.407Z","0.0.0-preview-448c42c":"2021-08-04T19:22:22.671Z","0.0.0-preview-87de44b":"2021-08-09T20:20:47.916Z","0.0.0-preview-028b435":"2021-08-10T22:00:34.890Z","0.0.0-preview-6b7a6b1":"2021-08-10T22:02:50.482Z","0.0.0-preview-69f2629":"2021-08-10T22:29:28.631Z","0.0.0-preview-fb27d03":"2021-08-10T22:29:57.590Z","0.0.0-preview-5bae82f":"2021-08-10T22:45:13.847Z","0.0.0-preview-88b6c16":"2021-08-10T22:45:26.339Z","0.0.0-preview-457a873":"2021-09-01T18:57:32.530Z","0.0.0-preview-cf43a96":"2021-09-03T19:25:10.371Z","0.0.0-preview-da40919":"2021-09-08T02:01:23.546Z","1.0.0-alpha.20":"2021-09-14T20:03:14.725Z","0.0.0-preview-adde714":"2021-09-14T21:04:34.889Z","0.0.0-preview-6702e25":"2021-09-14T21:16:42.357Z","1.0.0-alpha.21":"2021-09-14T21:28:26.684Z","0.0.0-preview-20646d4":"2021-09-17T19:24:14.988Z","0.0.0-preview-865b716":"2021-09-17T20:01:09.872Z","0.0.0-preview-643fa0b":"2021-09-17T22:13:42.903Z","0.0.0-preview-ed15b81":"2021-09-20T23:19:11.990Z","0.0.0-preview-a79991c":"2021-09-20T23:19:30.528Z","1.0.0-alpha.22":"2021-09-21T01:29:00.407Z","0.0.0-preview-e1c79a8":"2021-09-23T15:20:49.242Z","0.0.0-preview-3288ee4":"2021-09-23T15:40:20.597Z","0.0.0-preview-3028df2":"2021-09-28T20:44:10.886Z","0.0.0-preview-85a4345":"2021-09-28T20:44:26.614Z","0.0.0-preview-593cefd":"2021-09-28T20:47:08.023Z","0.0.0-preview-c41e8ea":"2021-09-28T21:26:18.095Z","0.0.0-preview-616ac9d":"2021-09-30T15:32:00.480Z","0.0.0-preview-269dea8":"2021-09-30T16:07:24.639Z","0.0.0-preview-d91d500":"2021-09-30T22:02:06.107Z","1.0.0-alpha.23":"2021-09-30T22:14:07.441Z","0.0.0-preview-36b8e7d":"2021-11-08T17:37:23.456Z","0.0.0-preview-52149b3":"2021-11-09T02:34:17.688Z","0.0.0-preview-f15713a":"2021-11-10T04:04:09.906Z","1.0.0-alpha.24":"2021-11-12T00:01:55.575Z","1.0.0-alpha.26":"2021-12-04T21:07:40.388Z","1.0.0-alpha.27":"2021-12-29T17:27:55.525Z","1.0.0-alpha.28":"2022-03-04T19:09:01.544Z","1.0.0-alpha.29":"2022-04-02T22:32:03.273Z","1.0.0-alpha.30":"2022-04-12T17:58:48.162Z","1.0.0-alpha.31":"2022-05-08T23:54:40.023Z","1.0.0-alpha.32":"2022-05-25T03:26:29.122Z"},"maintainers":[{"email":"brunohcastro@hotmail.com","name":"brunohcastro"},{"email":"daniel.osullivan@alphaflow.com","name":"dosullivan-alphaflow"},{"email":"chris@alphaflow.com","name":"chris.liddell"},{"email":"zakar.nahapetyan@alphaflow.com","name":"znahapetyan_alphaflow"},{"email":"gabe@alphaflow.com","name":"gabriel_rinaldi"},{"email":"josh.ayres@alphaflow.com","name":"joshayres"},{"email":"hannah.ponce@alphaflow.com","name":"hannahmarie"},{"email":"nscharfe@gmail.com","name":"nscharfe"},{"email":"engineering-admin@alphaflow.com","name":"alphaflow-engineering"},{"email":"jamesdarby7@gmail.com","name":"james-julius"}],"description":"AlphaFlow Resource is a library for connecting user interfaces to remote data.","homepage":"https://github.com/AlphaFlow/client-core#readme","repository":{"type":"git","url":"git+https://github.com/AlphaFlow/client-core.git"},"bugs":{"url":"https://github.com/AlphaFlow/client-core/issues"},"license":"UNLICENSED","readme":"# @alphaflow/resource\n\n`@alphaflow/resource` is a library for connecting user interfaces to remote data sources.\n\n<details>\n  <summary>Why do I need a library for that?</summary>\n\nThe task of communicating with a remote data source starts out incredibly simple. If you just need to fetch some data, which lives in one part of app state, which doesn't need to persist changes, you don't need a library.\n\nBut, once you have a more complex app, you'll probably have to answer the following questions:\n\n- How are we going to ensure what the user sees on their screen matches what's in the database?\n- How do we handle cases where that's impossible?\n- How do we keep our app responsive while we're waiting for remote operations?\n- How and when are we going to initiate a fetch from the user interface?\n- What are we going to show the user when fetch requests are pending, successful, or failed?\n- How are we going to avoid re-fetching data which as already been fetched?\n- How are we going to share the result of fetch with other components which depend on the same data?\n- How are we going to write changes?\n- What are we going to show the user when write requests are pending, successful, or failed?\n- How are we going to ensure writes are reflected in all components which depend on the same data?\n- How can we avoid or manage potential race conditions?\n- How can we establish stable code patterns that are maintainable and intelligible to new contributors?\n\nThis changes things. `@alphaflow/resource` offers a methodology for reasoning about all of this and exposes an API which reflects that methodology.\n\n</details>\n\n<details>\n  <summary>Why not Redux?</summary>\n\nIf you use Redux in your stack, you don't _just_ use Redux. Typically, you'll need use it with Redux Sagas or Redux Thunk because Redux doesn't offer a first-class way of handling async operations. That's because Redux is a solution to the problem of predictable state management, which is only part of the problem we're solving here.\n\nA more complex app will likely include a wide file layout for Redux interactions - including actions, action names, reducers, and (hopefully) a strategy for managing race conditions. Beside the uncertainty and inconvenience of making changes in this ecosystem, we end up thinking in terms of action types and payloads instead of UI data requirements.\n\n`@alphaflow/resource` was designed based on patterns that emerged in Redux/REST projects. These patterns offered guidance toward abstractable optimizations (e.g. a caching strategy). They also revealed an alternative conceptual model for the way we draw data into our apps, present it to the user, and pass it around. Really, the answer to this question comes down to which conceptual model works best for you and your team.\n\n</details>\n\n## Installation and Usage\n\nInstall with your package manager of choice.\n\n```\nyarn add @alphaflow/resource\n```\n\n### I want to use remote data in a React component.\n\nFirst, we'll need to describe the data as a resource.\n\n```js\n// src/resources/ToDo.js\n\nimport toDoServices from 'src/services/ToDo';\nimport { describeResource } from '@alphaflow/resource';\n\nconst ToDoResource = describeResource('ToDo', {\n  get: toDoId => services.getById(toDoId),\n});\n\nexport default ToDoResource;\n```\n\nThe first argument to `describeResource` is the name of our resource. The second is a configuration object with one required param: `get`.\n\n`get` is a sync or async function which returns the resource matching a given `identity`, in this case, we've chosen `toDoId`. If we were building a list of to dos, we might choose an object of search parameters.\n\nNow, we're ready to do something with our to do data in React.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from \"src/resources/ToDo\";\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>{toDo.title}</h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nHere, we have a simple hook on our `ToDoResource` which takes one argument: an `identity`.\n\n> Keep in mind, if our `identity` was an object literal, we'd want to memoize it before passing it into the hook (or pass some configuration to the resource, check out the API ref for more on that).\n\nThe framework handles fetching data as they're needed and gives us convenient access to both the data and any errors thrown during fetch. If this component unmounts and remounts, the already-fetched to do data will be cached and available synchronously on remount.\n\n### I want to write changes to remote data.\n\n\"Writing a change\" has two parts: we want to give instructions to our remote data source and we want to reflect the change in the client. We're going to co-ordinate all of this within a mutation.\n\n```js\n// src/resources/setToDoIsCheckedMutation.js\n\nimport toDoServices from 'services/ToDo';\nimport { describeMutation } from '@alphaflow/resource';\n\nconst setToDoIsCheckedMutation = describeMutation(\n  'setToDoIsChecked',\n  async ({ toDoId, isChecked }) => {\n    const toDoAfterUpdate = await toDoServices.setIsChecked({\n      toDoId,\n      isChecked,\n    });\n    await ToDoResource.yield(toDoId, toDoAfterUpdate);\n  },\n);\n\nexport default setToDoIsCheckedMutation;\n```\n\nWe can use this directly within our component.\n\n```jsx\n// src/components/ToDoCard.jsx\n\nimport ToDoResource from \"src/resources/ToDo\";\nimport setToDoIsCheckedMutation from \"src/resources/setToDoIsCheckedMutation\";\n\nconst ToDoCard = ({ toDoId }) => {\n  const [toDo, toDoFetchError] = ToDoResource.use(toDoId);\n\n  if (toDoFetchError)\n    return <div className=\"ToDoCard --error\">Oh no! Something went wrong.</div>;\n\n  if (toDo)\n    return (\n      <div className=\"ToDoCard\">\n        <h3>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={toDo.isChecked}\n              onChange={event => {\n                setToDoIsCheckedMutation({\n                  toDoId: toDo.id,\n                  isChecked: event.target.checked,\n                });\n              }}\n            />\n            {toDo.title}\n          </label>\n        </h3>\n        <p>{toDo.description}</p>\n      </div>\n    );\n\n  return <div className=\"ToDoCard\">Loading...</div>;\n};\n\nexport default ToDoCard;\n```\n\nThe resource library should have an answer to whatever you're trying to do. This repo includes some more in-depth examples in the `examples` directory.\n\nIf you'd like to see an example added or a use case supported, please open an issue.\n\n## API Reference\n\n### `describeResource()`\n\n```js\nconst MyResource = describeResource(name, {\n  get,\n  areIdentitiesEqual,\n});\n```\n\nReturns a resource.\n\n`name` is a required string used for logging and keying internally.\n\n`get` is a required async function of an identity which returns the matching resource.\n\n`areIdentitiesEqual` is an optional function used for determining if two identities are equal. `Object.is` is used in its absence.\n\n> If your identity is an object literal, you might want to supply something like `lodash.isEqual`. This will make it easier to retrieve resources by identity in mutations and to avoid memoizing identities in React component bodies.\n\n#### `Resource.use()`\n\n```js\nconst [resourceData, resourceFetchError] = Resource.use(identity);\n```\n\nA React hook which returns remote data.\n\n`resourceData` is either the result of `Resource.get` (plus changes from any mutations which have been applied) or `undefined`.\n\n`resourceFetchError` is any error thrown in the fetch operation or `undefined`.\n\n`identity` is any value. It will be used in `Resource.get`. If it is called with `null`, it will not perform any action.\n\n> If your identity is an object literal and you have not supplied your own `areIdentitiesEqual`, make sure you memoize the identity higher up in the component to avoid an infinite re-get loop.\n\n> Calling with `null` is helpful for cases where higher-up resource fetch operations need to resolve before you can construct an accurate identity. Imagine an array of `recentCommentIds` on a post, we might need to wait for them before our `CommentResource.use` could do any work.\n\n#### `Resource.yield()`\n\n```js\n// ...\nawait Resource.yield(identity, writeWith);\n// ...\n```\n\nWithin a mutation, write changes to the resource store.\n\n`identity` is an optional value for specifying which resource to write to. If `undefined`, all data within the resource can be written.\n\n`writeWith` is an async function of resource data which returns their next value. If `identity` is defined, the signature is `writeWith(resourceData)`. If `identity` is `undefined`, the signature is `writeWith(identityForResourceData, resourceData)`.\n\n> `writeWith` will not be called if resource data matching `identity` has not been fetched or the resource get matching `identity` threw an error. This stops partial optimistic updates from hanging around in the store, which can cause confusion and bugs. It also assumes that `Resource.get` will have the most current data whenever it is called.\n\n#### `Resource.refresh()`\n\n```js\n// ...\nawait Resource.refresh(identity);\n// ...\n```\n\nWithin a mutation, force a re-get of a whole or single resource.\n\n`identity` is an optional value for specifying which resource to refresh. If `undefined`, all data within the resource will be refreshed.\n\n> `refresh` works by calling `Resource.get` under the hood. `Resource.get` will not be called if resource data matching `identity` has not been fetched.\n\n### `describeMutation() => mutation`\n\n```js\nconst myMutation = describeMutation(name, runner);\n```\n\nReturns a mutation. Mutations are regular functions that can be called anywhere in your app, except within another mutation.\n\n> Because the library ensures one mutation has finished before the next starts, a wrapper mutation will be caught waiting for a child mutation. The child cannot start because the parent isn't finished, and the parent can't run because the child can't start.\n\n`name` is a required string used for logging and keying internally.\n\n`runner` is a required async function which executes your mutation. It will likely include one or more service, `Resource.yield`, and `Resource.refresh` calls.\n\n### Debugging\n\nThis library is build on top of Redux, actions you take will be dispatched in a recognizable fashion within the internal store - use the [Redux Devtools](https://github.com/zalmoxisus/redux-devtools-extension) to walk through changes.\n\n### Caching Behavior\n\nFiguring out a caching behavior which is intuitive and helpful without clogging the client with unneeded data is an ongoing process.\n\nAt the moment, we have a replace-only policy when in comes to caching. That is - a component un-mounting does not mean we should dump its data, but when the identity passed to its hook does, we can dump the old data in favor of that matching the new identity.\n\n### API Design\n\nThe aim of this library is to reduce cognitive load and repeated work around the topic of interactions with external services. Its internal API is central to achieving that. The library must export the minimum possible \"constructs\" in order to remain helpful.\n\nThe core constructs are:\n\nA **resource**, the basic organizing principle. It's a wrapper around conceptually related information.\n\nA **mutation**, a description of how activity within the client affects change to internal state and external services.\n\nDevelopers may also become aware of the **store** which handles storing the resource state at any given time.\n\nFunctions exposed by the library should maintain similar call/response signatures.\n\n```js\n// export is verb/construct\nimport { describeResource } from '@alphaflow/resource';\n\n/ called with a label and a function\n/ the arguments of the function are 100% under the developer's control\nconst NamedResource = describeResource('Named', identity => get(identity));\n```\n\n## Contributing\n\nStart tests in watch mode with `yarn start`.\n\nIt may be helpful to use an example app to test your changes. Run `yarn buildAndWatch` in this directory. In another tab, navigate to your example app and run `yarn start`.\n\n### Architecture\n\nThere are a few assumptions built into this implementation:\n\n- Writes to the `dataStore` happen in order. Mutations must run in order.\n- Only the `taskController` can originate writes to the `dataStore`.\n- Subscriptions on the `dataStore` may not write to other internals, they are only used for communicating with the client.\n\n---\n\nThe library stores data in two stores:\n\nThe `surfaceStore` is a record of every resource and identity the client is using. This is pretty closely tied to hooks at the moment, but it's a simple subscription pattern, so it could be expanded.\n\nThe `dataStore` contains information about active processes and the actual data delivered to users.\n\n---\n\nHere's how it all works together:\n\nWhenever the `surfaceStore` changes, it schedules fetch operations which are queued and run by `taskController`.\n\nWhen users invoke mutations, they are also placed in the `taskController` and run in order.\n\nThe `taskController`, in turn, conducts async actions and writes back to the `dataStore`.\n\nWhenever the `dataStore` changes, it walks through `surfaceStore` and calls change reporters within the surface. It's key that this does not directly lead to changes in any of the order stores, or we'd end up with infinite loops.\n","readmeFilename":"README.md"}