From 614b9aec333faa3d43afaa59297d70b21d35a6ec Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Mon, 28 Feb 2022 15:35:34 +0000 Subject: [PATCH 1/6] chore: start opensearch cluster on gitpod startup --- .elasticsearch/Dockerfile | 7 +++++++ {scripts => .elasticsearch}/opensearch.yml | 0 .gitpod.yml | 7 +++++++ 3 files changed, 14 insertions(+) create mode 100644 .elasticsearch/Dockerfile rename {scripts => .elasticsearch}/opensearch.yml (100%) diff --git a/.elasticsearch/Dockerfile b/.elasticsearch/Dockerfile new file mode 100644 index 0000000..1cca3c1 --- /dev/null +++ b/.elasticsearch/Dockerfile @@ -0,0 +1,7 @@ +FROM opensearchproject/opensearch:1.2.4 + +ENV discovery.type=single-node + +COPY ./opensearch.yml /usr/share/opensearch/config/opensearch.yml + +EXPOSE 9200 9600 diff --git a/scripts/opensearch.yml b/.elasticsearch/opensearch.yml similarity index 100% rename from scripts/opensearch.yml rename to .elasticsearch/opensearch.yml diff --git a/.gitpod.yml b/.gitpod.yml index ed048ba..2448536 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,5 +1,12 @@ image: file: .gitpod.Dockerfile +tasks: + - name: Start Elasticsearch + init: | + docker build -t hyper-elasticsearch .elasticsearch + docker run -p 9200:9200 -p 9600:9600 --name local-hyper-elasticsearch hyper-elasticsearch + command: | + docker start -a local-hyper-elasticsearch github: prebuilds: From 14c3211ee312736bc9ea19cc79cc9d3c8ef95bd7 Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Tue, 1 Mar 2022 16:01:47 -0500 Subject: [PATCH 2/6] chore: bump deps --- deps.js | 6 +- deps_lock.json | 307 ++++++++++++++++++++++++++++++++++++++++++++- dev_deps.js | 4 +- dev_deps_lock.json | 28 ++--- scripts/lock.sh | 2 + 5 files changed, 324 insertions(+), 23 deletions(-) create mode 100755 scripts/lock.sh diff --git a/deps.js b/deps.js index f4b1200..272f9f5 100644 --- a/deps.js +++ b/deps.js @@ -1,4 +1,4 @@ -export * as R from "https://cdn.skypack.dev/ramda@^0.27.1"; -export { default as crocks } from "https://cdn.skypack.dev/crocks@^0.12.4"; +export * as R from "https://cdn.skypack.dev/ramda@0.28.0"; +export { default as crocks } from "https://cdn.skypack.dev/crocks@0.12.4"; -export { encode as base64Encode } from "https://deno.land/std@0.117.0/encoding/base64.ts"; +export { encode as base64Encode } from "https://deno.land/std@0.127.0/encoding/base64.ts"; diff --git a/deps_lock.json b/deps_lock.json index f76d7e9..5bc0c69 100644 --- a/deps_lock.json +++ b/deps_lock.json @@ -1,7 +1,306 @@ { "https://cdn.skypack.dev/-/crocks@v0.12.4-Mje8nEhNx2rmIpwz3ROp/dist=es2019,mode=imports/optimized/crocks.js": "93d587d18dc5f124f30e5b38de37a6471eb65309c94ef2ffc7a36dc40ab394da", - "https://cdn.skypack.dev/-/ramda@v0.27.1-3ePaNsppsnXYRcaNcaWn/dist=es2019,mode=imports/optimized/ramda.js": "0e51cd76039fc9669f9179d935b261bcbb19ecbb11d49e7e60cfbc14360a85d2", - "https://cdn.skypack.dev/crocks@^0.12.4": "fc624b776247aa182539fed6fe1b21a578609d1c2c0e31830c4469f2bcd099cd", - "https://cdn.skypack.dev/ramda@^0.27.1": "a87add6080b004efb8072f81063306b03dac107cd2b6e5fca726f1a8bbb969b4", - "https://deno.land/std@0.117.0/encoding/base64.ts": "0b58bd6477214838bf711eef43eac21e47ba9e5c81b2ce185fe25d9ecab3ebb3" + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_Set-d915dfa1.js": "85edd0c60ae74c28bfd7a6d831d1274b2299de6c46cc19ddfed24f593365f0c8", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_arity-fefc01f2.js": "dd07d98af13cc6d84a8fc656ae901b590bbeebfcdff21a1eb9903f3d22a1dbc9", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_assertPromise-2ee5312b.js": "19db5ea49669f56d34c8e3b33b23e656a9eab5f0aa4f3c5c4607c1559f868d04", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_assoc-b7e88c54.js": "9d141162366130b370dd3d78ceb8a00b61231e2c7b15e3396718f8ccf6a9fe75", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_checkForMethod-9f9acae8.js": "4a71226762f5d1e7b7e94dc0d0fb6b45eead7a8f3d6002d9104d7ae07caba97f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_clone-7429370f.js": "26e604238d67b05d90c4825127a5c1e06ed015c9a395325898a15f5cfc594934", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_cloneRegExp-a38a2fe4.js": "854a15ba34b337e28bfdde33693504fe44ca93e0fb2bf14eb19722a42d3831aa", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_complement-ae234b25.js": "f32780f3324edf7f112267b463db1fd2a8fbb564668c45bc8fc9fcf850cb5556", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_concat-9dee354c.js": "083e62b9ff4a6e116c93bdeb10dbc8671d0abe03e23a79bb246109f5df082dc5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_createPartialApplicator-0d3fa0f6.js": "b725a20ec47f321c17111ace42a018cf29ccc866a1e74b25d8010b58349d74ff", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_curry1-a6784b40.js": "7cf61d0d2dd4f907fee43ed59475238e4cdd6e6661cd2cd724fdbfacc6046fa6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_curry2-21fa898b.js": "ad7c40d58ecc509f8c91651488639d446dba6c8d64f3f18e443b2902d216fdb7", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_curry3-716aebc1.js": "bc6de34606917de50398e623891e9d9cc1cc113f33f062c62d2646d889a6f24f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_curryN-f6b52a3c.js": "70fbc3cbf1edc9afe43fc4d9c7c71b14cf6af99f1690a683a839b374236e4952", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_filter-40fab1d6.js": "86cd84bb37fcae2d25ca5fab6a47b33e4252eee91e22e10b4d2f4207445f8df9", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_has-ef506577.js": "39418af351d44a42fd5bca80d0fb24e7aaad24f5925822188092fbfc243b9a19", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_identity-7918716e.js": "5f6838ba0de2a5087ec1dddeddddbfac938a6a04ab103e5d084d30c29c8f9975", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_includes-6b736d1d.js": "0d0cd8234a8a795ecfcce4a2818e5693e3cbab69ebef043543f55d19a13a64f1", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_includesWith-ddfe560f.js": "0edb8f61f9d203a3708d13a987c39c7d9fb91974aed2f044bfeb033b92510fb6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_indexOf-a6922268.js": "e52725a676b4d1c2302935f1aabc216bdf9656d1036255cf381f09a635d78177", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isArguments-6faa657b.js": "3ef1aa910ccb44e9a0abddab6b65204e9581be0d01253cf233a56c84579a6649", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isArray-3f7112bf.js": "81610e5d71a9bb5a5e74ddb58d99ea12d9b150450d653258ac867e1f47469d24", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isArrayLike-9455bd24.js": "b3ad4c505642457ad4da06e8f6bb9cfd132ea272b3d025ab29c3e86dcc2467b4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isFunction-f2ef089d.js": "a1a9f908528509ab333466feb171ef5f2292dc2d63e2e532f1af2d781fc28b02", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isInteger-6ed45b32.js": "3a4f992c76b40d7dc89d8fa7a4b28ed1eccbb39aeb9d78fb6a9ca1b625d399bb", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isNumber-30ee9efb.js": "277fde6ca0ab2adfb19b31c6f42630303482f254510c4b161dafd0203c91d064", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isObject-755da5cf.js": "c4823aea2d22399454c81e4c7ee1e8e98e6f191bda593e831bb5498f44401537", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isPlaceholder-69544913.js": "6b62541403ac8c5e13c946a8f646705e31b080f33941225d494bf8a86dbbc289", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isString-bd8a5e0f.js": "eba36bb5f1cc179efffd564c38b97865d2d175cff47d6b0adadbd924b39143a1", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_isTransformer-816ffbd7.js": "6cddb07687d800ca89a73953de591689a7c4394625be32493707826d3d45e0fa", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_makeFlat-50135e4b.js": "2bfe8b1c809df451ac186a85754654522b3774d64b3b97dddec0c60642876db1", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_map-855e0a50.js": "848e006ae4b8363d153f1475afd3557598986258ef3dcc28cf8e674077a4bc97", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_objectAssign-4e7711ea.js": "d25ac097eee74ebecc7a55f80da926353e5e6b60b28c1f9d6ccf9bd133e092c7", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_objectIs-99c5251f.js": "5409912ae88f218b0d05edfe08133c7e35fac0899adb895fc3d429b7ae302e34", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_reduce-6079ab80.js": "c88819531343f5a8c1e2c426706bd7a032c54ec9749726060427b136b47765de", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_reduced-43259614.js": "82f6ee4f50c30a46af842348fd1268a95eda09af0e04c35b885cdce84f6fcc8d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_toString-2b74d5dd.js": "917bd8cbda9d54564f8b26ce33c00aedbb499a56c0b33591579334f848eeba15", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/_xfBase-47118e3a.js": "8c94661a5d715a572f45566f7fd39bbaab8d9b7a1b1e39372885e05e849c4a9a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/common/dropRepeatsWith-841a3509.js": "e45816d11f25bb88a997d2bcff7221f24576c2691d20048ea9c8f4d4fd84d652", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda.js": "f163ed64bdd8abfc7c98d52edf83033ae3e3b6e84d1bf7f9b33177313a88818f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/F.js": "6ee70567e140117e38bf6f685ef898c2c3eeff01e2c5cdbee9677c71b2f5dc03", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/T.js": "da37b88cb476922b8296ce3eff7a5970d37462beabc07bb25d551ec6a6c24cfa", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/__.js": "af16340cf8cf667cfddb57a82b935d6660375daa5f1e0e9207ba3ac9e7e4b3c2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/add.js": "36dde3fcf71381c7baa295e0cb06b6e4ca5ba842dd563f4b86c34e6b6673700c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/addIndex.js": "be86fe45cfb3c570f2ed824f84b122620c6cd6559c99fb8a3bd00c217f16a451", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/adjust.js": "430a2ec10bd0e9621da6b7df635715c659f4adc3504550fee1f4330b1f97e2fa", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/all.js": "ef58e233cdf8dd01a6051692472adc1c237414c8f710779bfbe596cbff089b8f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/allPass.js": "c9e5a20ec4a4eb403aa8ef9179b06fed82b3f20fe1ef20ab0813d6eb094df514", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/always.js": "d87b8d4469f36e69917fd690a196ce8202493e27ff9aead479a94c7bfda8d1d6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/and.js": "7f3bae7517d2376621bc337ba1e71040613282958415191ae5383fb4d4ee7fd4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/andThen.js": "ad8e7eab6160324f39bc92b53a4ab3599263d9dc223a545a3895a7af735d4049", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/any.js": "41cee0e5a253bc0c187ceee5a214b0faee9d6124f11932bb833ac714a3edc554", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/anyPass.js": "e08d1958ba46106825d5de47022cc5ef193377f2cdf5c6b56e2dd915ffe32b9b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/ap.js": "d29bf67374cccfed0bb25d189ac526c2c751732e2cb5297d5f90a9c8c708b3dc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/aperture.js": "77b7badf3539ea7e11615bf727282dc13dedeb67fc998fe6781a3d0fa8c2ec1b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/append.js": "c2d446c58d3163eb2f9c0456b1e6c8819218002c01ab689250f5d249c5b016e6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/apply.js": "4fd9bc0589af63996148ff3f76207efe8c04a2c67dff3e36149991c960eb8ee8", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/applySpec.js": "a2757a31ce25088f39fcdd34c72024493535a19c9cf51bade57f82b1514067b2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/applyTo.js": "a792f0b0ee88b8d5d6dbec00e1f9b0d75b21550fd95c99f150732b1bb7bb334b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/ascend.js": "7ac9281fbce50d978ceab0fac3dc67827e1c766c6fe6f90dbd955c85f7b492ae", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/assoc.js": "f721b5be9f5cedc4fa8b3d6226d525e12161177c94e259ccc46a790b926f2863", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/assocPath.js": "728baeebc3302f25675ebe28354820e734eb523d2bdd6fd54e27c95f8dee4e05", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/binary.js": "c45ef41041080a8e0e8372bf0507236447a9439ac19dbb48eba7bf20aefcc490", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/bind.js": "8d4427d9b7f1db7470b5144a6d438604a470b491948f33f16be1e35d678ebbed", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/both.js": "0d68c2e8ac646b5a122bb5f2c816083a4b946c1283efe62ef46e0f1af61d9450", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/call.js": "b87c6b1d096c73ad747af5995c6306f4008e14ef37db0d6bb4a0ebdf0f66e174", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/chain.js": "20d504ede70c895c551931b58f44a820aca5cfb5630112f2db3534515f6e4e7f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/clamp.js": "0828943ccbe6d80667538b1382e849cb179e6e1a86b9c07456f46d8df4c5433b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/clone.js": "2a4eb8cd8ad0af8adf50bcbf5ffb0247737cc908f0252f6ec6cefc50f4d3ccdd", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/collectBy.js": "9a49e8153acbf1066e9db2fad712e76407d02d66e1912ee7b774db2318ca8a0f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/comparator.js": "ec1ac86ba13b78cd4d80dc2be00784ab12989a94a33d0b925a355637b486015e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/complement.js": "f74f922d6bf285c0ab9a296839d93fe746a500792cf511d06980e8fe6532b80d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/compose.js": "06a50fd85cdb726d56ebae876eed1594f3bdaab2e085f993296a218dcbe6e1f3", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/composeWith.js": "50c6171cab79db15e486b9347e9b3a89a5d808c592fce71b6c4cf6918e6690ce", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/concat.js": "ac1a4888b256c7b6fab8714622f50a307590d660940b3ec1a254a7813b37c582", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/cond.js": "65a815ee0b65397e6ea26ae5e420e3bc4aabb48d97dbb9ae6e12938e033c9aa0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/construct.js": "14184f1a1975e36b155bb5a5b82fee88b24ba2f14b82af1281a730306ee26f70", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/constructN.js": "22b4cf19e6a9b7550fb5d1a4c4d5325e72917b47fe4412ed6b8c8486aa2cc573", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/converge.js": "7dead4330b884b829d30d6544b8fe64e25522d3db6fd96772562005c2226fa09", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/count.js": "7e0f9c712437279e355561e780a275839fe21145dedc5b5a9878568570e66c75", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/countBy.js": "b5eeed36ee19e6f26e89a2e4c4d2fec43174dabeb347d5850eea5ed7d2f6c139", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/curry.js": "2f51467cca4c697d8837de50c518074cb9e962e41d78a67eaf593fef67cbbc1a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/curryN.js": "ebad5e9dc7ed8f90d01723dbbee5706b374d752955b44525e449d63436ea3090", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dec.js": "66eebad6dee843aca7fdba79fcf65be7fa9ef300aa347383c6bb850849359f00", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/defaultTo.js": "24528fae7093bb5bf3f1fc3ca8bead3b8813fff5f4cb6184f58e5149b177f7b3", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/descend.js": "f7156ad6160f4843c0fc8301583ab1d9740d12098bddba567288f913ba27bf03", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/difference.js": "e11be33dcabb254e7764e787d0a3dc9735d05540a0095c2ce27519eae5b50daf", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/differenceWith.js": "4797f8002806503aaa7e67ebbb0aeac36ec072938944fdb1cfa0f3f070b681c5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dissoc.js": "39cdb55d24899f07d6b06ea7d86038d42f6b6dc4e35a1fc686365b1f92115b15", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dissocPath.js": "5872ab2afd65a7cbdc23ad3854492ae94e9b87e069c43370c3ddfbfa43530945", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/divide.js": "e22989e6ba381c72c5c29c63d86b4f91a1e0e5748512a1a644ede1503029ef99", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/drop.js": "2652317c9ce62afbbdf61ac67710bd564b0ab291a75f9f9b643500d455724b1f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dropLast.js": "8392bce9c3aefcacacf5e87dca2f326c6c0af79593c89b8086dfa7c62af29c09", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dropLastWhile.js": "f998c3593c5ca57baf5a278334b3e4e1c25b9f57bb6493d1408660ea55c63553", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dropRepeats.js": "5584704411b6f9a2791cf8ce838c1ad20b7de530e679418d7bd5906ecd91c535", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/dropWhile.js": "4c3f89bf51d49d3b63275534d9cd8758fa2ee89fa63a6ea7ba8b8a3b52fce361", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/either.js": "c0838dc99c3ada6ddd159a68d7df0aa6f27f5b78112f29565e892ad96bacabfa", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/empty.js": "2d81e26626831ef7f4bdf6e8727ec20e4028bc2bdc4774eafc445e2498c21edb", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/endsWith.js": "93f7446ca80b530e72d638f4fe6dcaf62f00500658626ee2e997317059272020", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/eqBy.js": "b2d8c41c072d63195feaebbc0351803f9fe05ff6cc7feac01c45fa25dfab2d42", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/eqProps.js": "f4e4a0de55f8cbe92d3af2191df260a66065e0820bc9af0bb6ef6e2832a903c5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/equals.js": "da081ddd60bf16baf9c201329a9c63fae7380d68b9b2898188115e4c2d598adb", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/evolve.js": "ebc63cd1cd49a8b4461d2bf397beed55c49e96f449919ed2c247b5c21fdd7f0f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/filter.js": "fc92a45c3dc6614d45bc938352604ba8e5562aa9e2ce6335ae888a5f9be50614", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/find.js": "cb3e37dbe1eb479c51f63907e7ec04e25cf24e73bbd8b4292ff5059954722bb8", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/findIndex.js": "590f749d6628134b05a1d068032ceb63276fabb68836cc9a3f426a12bd9c9b83", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/findLast.js": "6f5cc1612d7acef94a8f1d30f271e769153dbb3570cd712e072d6777fb6b1808", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/findLastIndex.js": "9937cf8d4b2552ae0b71bc53e224c256f64fd4629ad531c360214a43b88233ff", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/flatten.js": "4afd269ed9e444082c7f1f82a5e9b1b26d0bf6f07228d9d57fabeca48290b2ec", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/flip.js": "282641165de9f0ab67496c0ed8a23c3e270be33f1fd1f6713537c5d635e04947", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/forEach.js": "d430cdd04f632520d1e59201a63392c10cc177aa4759b5e5b8df25372cc2c1e0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/forEachObjIndexed.js": "656180242fa29cdfddb6488caf35bf9cd657591fe28642a09aa95c0aa49e43f4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/fromPairs.js": "efce65be5dece8813c3cabf6ab855622abaf591b6b9ccff6a3c953f19e01fe7b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/groupBy.js": "6df5006fd047eb8dbcf25b0b44850988656425b28a84ab429622a99980059ffc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/groupWith.js": "7cc860d5cb4644f365a44ed25b3f95a4c8e2a0f5f48ad41ad29d6e07366212bc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/gt.js": "a3140dbc5dd3cff7b5b601692bde88bdc752703b3fc97789d65be186de43d8cc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/gte.js": "bc6964a4b5f44fedae669521f1fd4b4a8f7e61f9337d0d03324fc7d0400b5a15", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/has.js": "a77b10375a1ab329f0636a39141f09b9c2dc3c10f3185f09c46ada134b9371a4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/hasIn.js": "b59888b0fffacdac3dc887d2b37b746ce591fd7d4aad51c6e577133ef1e5e658", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/hasPath.js": "ba0e891cc26a3031b6c4eb9b1b1db59fcef643d92af86006a2c0018b470a66ff", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/head.js": "47f346b041c0a639d76ae88e79403016a81b8a3be24fd2f0496571a29d88a9b0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/identical.js": "47fc4a4a2479e9a6e5712f7f0a1b8f5982c6181c89d62285d49f7a51b85b3421", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/identity.js": "44c7e21a4db23a965fc7e27bfeb160e5a5a4f7b3d314a1d911d798425e01bab4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/ifElse.js": "21e7fa3bf0d0db253f8287c3d26ac230c340ef5d1de64bc1b57299cfa15bf56e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/inc.js": "2b4102ffd1cd9a8dcc62ddd3ba5cf1b02f05f789ecc5509a8e2bd568596f9727", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/includes.js": "bdec5749b2974aa706690ae314236cb9d3ebb9a0830573aaeb29e288a37b04d2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/indexBy.js": "cb77250da2ad1e94c4dc54ce18f0441391b6ace2e750b55c86caacddf8751337", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/indexOf.js": "9407056c549188f2f63905816bac6e429c5b513f07e5899d751086a38fedb4dc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/init.js": "b3b4b041f365ed334d2c078116c944e763e74ce0b54976e988b77e634f258052", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/innerJoin.js": "722ffb0a8982550f7826d0c72115fb016303469be75eee73f05302a75fb3251c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/insert.js": "8578175027f66cd2562a7d1b14cdfa240da5843eca1faa099192ab1e6962210a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/insertAll.js": "83bb56645335ca102727b346e65f32d7e21dd213d05d94d77f6a6eef838c7a37", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/intersection.js": "e56cf8faa5a1429808d4b1b4297870f7062df314950549534a4b33b04d02db82", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/intersperse.js": "070e5ca8b17c9c4f8917c7595eddc69c122a235d3a81183517f0e457a636ec99", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/into.js": "2685f30d6eceb6dd5450561ed5afe9483b34f6342b122d7d8d5a4411a31cd0fc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/invert.js": "6bee0a0564463c7ca5b67f7ca16ce07554d24c7e8d9e297dff0e75473b378664", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/invertObj.js": "207f69f8b9d90f92a5c68a27d038af78235e9e1d217a00555552d3212d45e339", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/invoker.js": "0fa211805ea3b47a44dab1cd8adbb8fb0f1e69949759ca13fbb7998e23429917", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/is.js": "d6d43714279dc194c7c7645dc1a8abb1c4740377ca615a6421d0ed44d0d8ac95", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/isEmpty.js": "adaa7dc712fe2ef123ab7a99ddfc0258284b2f186ed63092c375d9a2bf4f7fc0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/isNil.js": "10c86d1dffc7b2eadd525fa23bcd9f36bfe8bb9038c0636eb4a945cdfeb5c528", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/join.js": "0dea08fea5680fafbe68f9bc6dc26566ad3c0c78131b01fe445a9fe272c99aa0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/juxt.js": "42d421e9bf500a146d685b0cfda881e1b2a99a66ea67930aaabc398f5cfb3892", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/keys.js": "4579e0fe74f9c7fc3341173ab40d9e21fdbf83b1ce88461fd48964cae52c42fc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/keysIn.js": "a72b8e544ca2d6e2eccfc2b0d99c932b75b40ecd6f6340d761203f1c8b2ca04a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/last.js": "9b2a08a04868a564716f63a0feab7ce97b2ecaf91733e1c2ade1a40e339253a3", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lastIndexOf.js": "c020bed1f16e7e147c123f8d979b2165f3c9ae47d0ac55ffe6b49f9e9b0bc2c6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/length.js": "c59b7a19ae186177f3ef310924cd46ae9b505cc5dd37e7f0248c9afe7f7c8353", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lens.js": "775f76fa8cf26c612adb57706ffe389ddfae7e4f967974b0bd9f6817b3e3404c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lensIndex.js": "79bcb42c825d352f0687da3c8be230754539eb6024c116d5ec9f96f6543b61a4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lensPath.js": "9ef7d0921c020c071e373e39b360ace81429977a0675c777c75025a8723488cf", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lensProp.js": "ed994d0203ae53d8b9a03b6031bcfddbf92220a27eb876f16e529dd9244eb16a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lift.js": "2b82a9ff3af8fbafb762a337ed4008b2c1e14b1429314ee47857e7c45bcb5c66", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/liftN.js": "72586eb300a7acaa008d27b88cd0186a86b0be8232e5c7cb1f8f9c2f506bad9f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lt.js": "4e211b1a31f12730e01128ee59808582736429fe534563e7943189b9d71ed7bf", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/lte.js": "a3cbafe7effdbfd3a99197c3dd972f1d12688ae68373fc34316dd6c044d5c242", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/map.js": "d3590eebb77335e793c230b2d504ac8742bc8c5378cb2eb43492119ea0eb5988", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mapAccum.js": "2ba3a1ddf00c581a002f793f4100cf5a0eeea5e0230b545f3819a546c080a587", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mapAccumRight.js": "37963036393bfe4c3ab8bca21b7bd24c42330299011bde257d307d52d8113f41", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mapObjIndexed.js": "bee6f30cdd96f9b8352804a098200ec28f0def7da55b681de747fedbb3fe7cd9", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/match.js": "ac13ba48640b1e9d457a19084e37b3c5616df174ff9d98229ca9cbd7a1c3facc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mathMod.js": "39805322b6a214f586e572b1e8e53b838acbc5f1d5e83717d3c4cd1f4ec192dc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/max.js": "6096d9e8b72b2efddebf232d2ed46fad2a67f1246fed088e51cda43c1bc296f0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/maxBy.js": "04c37e3210e7c60c59800f0bf20b7cf70133bf878ce25a189c0d52227119493d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mean.js": "6ca3f3b6f86e13d8707e15c7967aea683e814ad2dc43b843a7e57f09e6707f38", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/median.js": "27521cc152571dcf6c6bb239b70d1a1e0d6e47875f36d975715a581c4162030d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/memoizeWith.js": "07bcdfb2b9526c57fe4705de06cdc0c2f413023f24ad84f5c219541d8a621398", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeAll.js": "672a2eb138c748adb66ffc0f1e26ed8bf04b5dfd3e65fce5b1093b89923d6d74", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeDeepLeft.js": "3641041e690ca04a71436fd3439add0d7e92e273bc20ebc26d9b1f51f8651826", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeDeepRight.js": "5d0ef368d0315ee8dff3b96e455497a26ada0317f4066c4847e5a29939084454", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeDeepWith.js": "fe70b9650c2478d984423d9265d0c82c139ce1007333f060cadc1fe471722e09", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeDeepWithKey.js": "a057ec465c20558cc4b7b73cbac3f3338d4c7bc684a3c607228782f3391db997", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeLeft.js": "d7363ee3bf9a5e441d40acb96e8457673021dfb9ad3dd6b3976774dc5553b136", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeRight.js": "ceaf4c3fae766a8b3922f495ae299f6d5a0b12e59678dade5b0e049f3a55def8", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeWith.js": "1a0564ffe89a4795005c7093f3dfa37b7891c40dfde0d99a92db6105e6d08101", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/mergeWithKey.js": "5636fbdca536c3ab8a8ff533e966cf0a09054f1bc0fe90119513f3eb81ae8063", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/min.js": "db9ecbccf37656fa6f3b80587a58ce8cb649aab3c8eeb1b142cc4d631497fe06", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/minBy.js": "9e5da935fba71e35bfb9d459b267dde5cc5e1f5889eeb8f9151e282ef54c7576", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/modify.js": "c13d1d2705c6369745332ae2a25badb33cbb6efecf0855b39da1c373e5c4c78c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/modifyPath.js": "47fda16c98ed1b5d876597f6a70775908ea1ae7573cad4c85bfd721821437d3a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/modulo.js": "06da13bd4f5f8d319c23eb6611419c32c7558409b24b1d0dbb749ab99e5e6128", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/move.js": "81a6e0daa9eb21d16d0f2078fa9966650ad449f2628c41f53a18d87bfb696212", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/multiply.js": "2137bd9e103a0dd44aeee23a681cbee1657199aad8967b6b2fc3a6d17caab16d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/nAry.js": "486bf006a670f114de284e884a6dd19761315bda6bb6bf3a7e4bb7573c037120", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/negate.js": "bd862208376736d4d9e4a1ff169e408a8c9c8cb057342dc8aadfec9e2533bdd0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/none.js": "11e759aa1f063f3a949b564c3e9aefd2259379e52c84415ce191e5f6de7e92c6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/not.js": "40347ba3295358beb226870ef83bc49e97bc2c17f3992340f0935a920c35a2ed", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/nth.js": "524677ed020f195d432193d7e48691a7d1e0264bddbb63429821b2d865f883d5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/nthArg.js": "9c193a4736b2a4fdeb30255c5422445845494b41a058e23df1b8750db12d5405", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/o.js": "0b52a698fb2296a964ae2a2b1eeec7b7d6f4f948eba4c46bbdbfdd39a8e489fc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/objOf.js": "b27cba0d964cd27ce74d92ecddc752d656ec263db32a076dd499ff8ba69d6650", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/of.js": "a6a01db140d9403d03377d096b164c6309b9c99e0f139d75a41c3ce8502f6997", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/omit.js": "ad649f7aefdbb5aaa94fd539d889b745b5f0573d07f9b58c335c166760f75fee", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/on.js": "a139653adce67999b25b108bf3f58a7c891da0005c23f15194772762e6eb58fd", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/once.js": "ca6f5eb704542eb2f489f5aedea040d0d66b8e2097c64f6d15c6a70f814cd489", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/or.js": "1a957ed10d6a83b32b81c2f435af3571cfa3efd8eae754bd3284b0d5407efd0a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/otherwise.js": "f1502a65666243b87b0a3960f84d3f1c8c3dd0737961e55558ca031adac5d7ad", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/over.js": "cc7bae27e0df58e2de7e3a11671be54139c524d1e37e457b6ae166f085722ebd", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pair.js": "bc870b9aba8f93cf79b1187e27c8424cfb83005a1820701a0a1b5180ee267def", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/partial.js": "11a8e0210bcf35451cefc4953e1ff324e6a4e919bec8a6e2441bc6efb8dd12bc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/partialObject.js": "2f444453acc93481aa3c97c47ae6448f3534b50175ec64ae4945355596075638", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/partialRight.js": "6b8a0a11f151e345db3f1f4187ecfd79305cea7ef864ab913a583f2ef5de89fe", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/partition.js": "cd604394b0b17a73724874e81cc244391b52ec9ed1471dd6ddd16f3407a59f78", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/path.js": "855f053368f705e03acd2a22d5766c61d8df9cf1588636a5645f46894f2ccc8d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pathEq.js": "d0e394de2968615326ed61e0de9105f4e3fc886fd7d54776973f5ac14d2b8e57", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pathOr.js": "8b1e271c189756d6aff01e75e3933bdfc09709a8fee657c0a2ba1fa62a6e60d0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pathSatisfies.js": "f54d4c0521734effe6eff4e3f9e4cce19ad72cc59f577e06db095e6bacbd9475", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/paths.js": "140f69caa5d753688e26b7f078cd779522ee2e575cd080464cc8e08c65aac43f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pick.js": "04186f190cab58a21b0863b75229c33d49629a2370f574883505189049a831e2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pickAll.js": "de5d461ba3292d5567aecdd74aabd4bfa5d78835168ba70cffb1fc0d2ea52158", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pickBy.js": "cde77ae2c6dad9a8d99bd2d28ee54b311a70bbc5c9c9bbc8fcf619b61c653c4e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pipe.js": "4e659e603b49b2ff7fd5e137721decea8dcb6ce235257d379f9de69e1dedd1fc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pipeWith.js": "10940ada66a264508b4bbe5b04569384eda6bcc36f7bdeaaeb1d39ee473931df", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/pluck.js": "7bbbc0efba9391fd7a924e214ad75c2f098de901e9510be30ed090419a4f023d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/prepend.js": "a983bbd1e3f7fcf6ec10b79a8d250ac37746811fd08fa159b491b4a2e52c153c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/product.js": "71661aed4bbe29baeb2bb680bf0876f652512048ab3331408f3afcd226cd87d6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/project.js": "34083fbb7f8a54a1d3abef8b573f9acf2846ada590c7f18e24592fba6ed95aee", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/promap.js": "e41c7ba7e4fd20b891cc1d229c50c38defb3010f878c6837e63197c0b2799664", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/prop.js": "b4e6c62e63c367443b3ea8d6b90c4181b40ace66be76b301ea2a8bfbbdc1fb41", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/propEq.js": "9eaa98cdb0ff82e49b056e4e810849346b81394ac15bba7fa693fa8f6715776a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/propIs.js": "1bfa58e6de648bdaa46e19d0f4c1e6219c30c73a65211a8ae7e1cac90d7ff67d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/propOr.js": "cd749e96661aaa506224d8fb590eb55400a91e204dbba4adab6600c1a21fcf1f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/propSatisfies.js": "8c85f7cc351ab4a88441136549f1b248b2ca4fd0ff05b09d7ca3b84b05731e57", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/props.js": "281952c12c401faab2afb27fc587a10e07925b554c13c7f18478c289335aad08", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/range.js": "3c6e55a70cc6e5a0da77f458cdba4580a68312ffea34473f8594003e26a73c56", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reduce.js": "894e08fb8658e5d5208fb2fea2f5c1f9264e620e0e4fda1912bfda8abe3764d5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reduceBy.js": "1b7c59d3919e0365c302456c76157d3d8e6ca80329ff21b344a8003832b28f47", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reduceRight.js": "2db5a9b3e834a87f0556657e2aef5119d0b0f24e54076e8e337eead265bebd33", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reduceWhile.js": "bae471da8ba40d7a2eaa8c2b56b5bf7c438c71c64aafd81af40ac9f701d7891d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reduced.js": "72f658ededd8487d04cc1a40e9768a8a9bb866b00ca49a37a11f450bf0ea813c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reject.js": "01c2f4edfe57963d7c0d400f53a75554ef1ad348728c3c69043d2926c1e6cf90", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/remove.js": "acd98d3293e197d22fa7bdfb3f3e1b68e27df8624bae26d183ac28bd198a8306", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/repeat.js": "98c63aa7be304faf5126ee057d66d1318983e3876917856ff4ccfe561c8b5233", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/replace.js": "e41ae3e0895bee27312c8b7732f855769724971ccc4790ef7585f5b77ee96c92", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/reverse.js": "d639a5f481bf916c44dc00e3551a4d498365709975a0c064f303c05fb6e8fb43", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/scan.js": "aac28fa05cdb684cd4dd4312a229176bd23dc1987f2a3d8af11c4b37ea34a001", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/sequence.js": "5036ebf5cac7f96185147787e4c19ca01f766792e47a48de8d669a72ad617ab7", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/set.js": "5905952f79cb4d876b2be179370647e5feb093d3bd6993bad7631e76bd7d6d9c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/slice.js": "938ea84d5a2125803589ad44aabd02a5311186541c92b644861829e075dae172", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/sort.js": "905c9a9a91019b56e529eff44c7bc6adc5690f205d415c30a3db647adad26389", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/sortBy.js": "c1aae2d5fe8a3bc3d7f4e13a3e673da6765f1bffa269afa3d389a99d3bd77991", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/sortWith.js": "b6e4d4ddfcd54ac6b9e02bef5388fa04bc5846952008ea4802d7b961b4255770", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/split.js": "0de8487e8e9e7a5cd83bc914b47777c7fbd362ebd8dbf20090f8d45faa24bb85", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/splitAt.js": "7c15bf8d47089650c9ade807540bdbd5da634b973cd6acb14cef0b95f4b94a56", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/splitEvery.js": "06162b25763d932409b6a1d222f383fd372b76dc2621a0ef5979950fc77ff891", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/splitWhen.js": "de9c10435c911765acc1749ef0299ff451ab32048dabb6d6644b06cd664e9d3f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/splitWhenever.js": "3d65043c5d03dc6ee9052fa92cc57e762834b9376bc1ff19d9cbf7870649fb7d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/startsWith.js": "86e94ea78a090b113b72516ed8f91f80a2a0e60e5a582c1493f2fe9069eb8b4c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/subtract.js": "c623327e32239f8d943eaba8372e1eff1bd13dae2dbe65939886bea389b2b422", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/sum.js": "b8ce7ef4f863a918b65892c72bd084817307abb08df9a218c1ebf7394e3996dd", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/symmetricDifference.js": "91e2ec204d3231597651872a1d42829a58a15b5f2f33e36c7606ac33fa88642e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/symmetricDifferenceWith.js": "f89feac24cadf844adbcf3d0757ae69516f218742b51d7c4d6a6c2da983e22d4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/tail.js": "30ec7f8556d3df2d8d6e80b72fda8371277229f9ec8b908b9aa70dc6f8ea685e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/take.js": "75c2120e0c04bea0e695ecd022087bf3ec9d35724708b1fea61431ab2f3f5853", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/takeLast.js": "d770558d504eb32a5bfea53737231ade40aff792dbcf6d346df9f9dbd8eebf8a", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/takeLastWhile.js": "eba2076d0c3e8958acfb6f69fb319c7c6293227c9a30be310f5acf09e1c1eb79", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/takeWhile.js": "a648842d40192b8099f6ce6d95ce641c50cb9c9f274229cc68290e3d08ea6511", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/tap.js": "c19f14add4d6445ef9260c32ccb78195c064d7bd8fce2c6a15249c8dcf392011", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/test.js": "a50a6b9e92b3c1fad0e2be6c0593243f1097ef43523184d98fb9671cd92106dc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/thunkify.js": "e69b6d6ba5f2a386cc2a7dd250b296ca2a2f66dd3b5d06799ba4ac02032ce828", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/times.js": "65283f9048ef8c773f4f6b4729848380103dbabc4915920eb957c6556c5dc924", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/toLower.js": "05880cbfb5f495827e29e8cfd6d59833ef8d8f424aaf84ec8199dd71730fd9ce", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/toPairs.js": "314c2702cbca97b812fa07aaf9703f9cdc83b2cc5f7895bcff35b6fea4c371da", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/toPairsIn.js": "21adf4785c7842799a17b10d03fd9439f61f8b674b0ad9cd0d08bf8dc1436cf3", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/toString.js": "e0f03ff03c2feaf03e43de56a13c1597ef2f1ecb8d52f3dfa80075c6872322b4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/toUpper.js": "8f0b4ad0a6410ec6aa8a0fd9b60a7ac34f78e65589c206bde1dd871d8cea7a64", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/transduce.js": "410607658b539d74b57123df743efef5b4e8d5662c4c3043aee8d1dcbe2da4c0", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/transpose.js": "9a51d58b7961e9793fb28b707fcd2791da014a2c3f67809a5a267eb90ce8decc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/traverse.js": "b4cf2245bb7a8d3a6b80f3b81d7886de594c66fc743b9fbaa3ed86efad2903b4", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/trim.js": "6584e0f1a19ffb7f6ddf058d7bcc830c7bffee7f408b67c9d0ece4e8d64667d2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/tryCatch.js": "2cf5df572259585f1c7aca1bdd9a40daeb297f52569b2c1ab5149cf73aa54d72", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/type.js": "3cb794157d49e9cdb95e789e533f9e5404c40b34bbde5d9a3cd076e37d621cca", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unapply.js": "18b365ca96e8e6c37963d32435d5c421f848da2961668dc3e3e49f3d621417e7", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unary.js": "1867b271ad219d1316261149a8276db953174beca9c68b2a394e98fb8f7629fc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/uncurryN.js": "3ef4d75cfb04273277063465911bb625a326bea585071d0bce2920d497e5968f", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unfold.js": "b597e95a36c9963b7ac9740a9fc305f13743ce2d47fd9c86c9220e9c50e43f8b", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/union.js": "b77db979542c0a6be2cc58b2eb8b378db1cdc81478a01273afd35c547bee55b3", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unionWith.js": "123ab7f10f6a0109016664218afe3a588613b06d01733e3bd1d42b8b5b1eed99", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/uniq.js": "1af9c0a02ae4fffb0864eba7d45d31338199aff916182d48edcdf3973b49d49e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/uniqBy.js": "1a11eeb1666c0c98da6a4cc2a3c05bce8f7ac3e0a459a60bdfe720d11ced8c76", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/uniqWith.js": "399f57a7ebba089dd3618aa2b5d1344aad3eedfbdc0386980406f6348ca656a5", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unless.js": "d172c0ad8d5256b7915ac99e937baff762c52bf971219daf6f3d14cab8c97bbc", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unnest.js": "e59e2cf591a01a567d308f58f1f91b304162228cddbfc8766daf2dbeae69f819", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/until.js": "523efd74535643b4cd927e75e3f5710a00deedc44faf5f889cef9a65847b0082", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/unwind.js": "56b702187969e4db6fb51b991aa7957890dcb00f482378dce8071dd01b9a6f97", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/update.js": "a4724e7148e5a1f5d7bdfae94062dc7ed0f36d91c3a2f179a995ce5726fcb82e", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/useWith.js": "29795014bae6ff2272f4fa16d1a2a2eeba87c231d02574b8162276950d8b88b2", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/values.js": "94e99f9dbed01780e8e4d770d059bcf62f57c336e79f06631070bec978a603a9", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/valuesIn.js": "4c0c78e300d62ee3446345dc0ff365bd6fde4aef8acba962c72c5b2a67adb963", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/view.js": "4c8a90b5de91d8d20d23b183aff350500f21f130ae66efc874b3c658927f765d", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/when.js": "c526985e3c388f8184ebc3b298ebc92465da856f656c29dbfec9ffbab6377b58", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/where.js": "e21b5e83e8355dd5c377a01a04dd0130c388fa82daa0172e75670bb49a859742", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/whereAny.js": "c6e12559c5d2bd32cc7ab847bc0db4fcab8e329b66a2302bf5f02fd848221f8c", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/whereEq.js": "7df5886db527ca43fa696e6b5cd92fa3ddb71d6de23c92f95969ac17628428e6", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/without.js": "c260b3cdf473c9dde66a43ae4aa653a11fef510e080d9463667d98168a2f53b8", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/xor.js": "8072e934f7c26d1b9d9ac3bc14adac99bee617b8751067203cb98deed95565fd", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/xprod.js": "803badaaf5a201c70bbe1c91515aef28c9577b19fa9e44e69359a8d2611553ce", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/zip.js": "a9d0dbfcc448f5d12a4dd996741474376421b0fac801ea5816cd532770674e89", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/zipObj.js": "9f6b44228886fd55ef22807893681174f066b9c9672b4eaca643d3cfb393c5e9", + "https://cdn.skypack.dev/-/ramda@v0.28.0-vDL6KbwpPkq7DJAM324O/dist=es2019,mode=imports/optimized/ramda/es/zipWith.js": "51d7e74be0dec2929186dd9b46802c836d5aa9b7a3f1e7b0f42bae3dac739345", + "https://cdn.skypack.dev/crocks@0.12.4": "fc624b776247aa182539fed6fe1b21a578609d1c2c0e31830c4469f2bcd099cd", + "https://cdn.skypack.dev/ramda@0.28.0": "ac1e3c64a2a3491971bee55f88faeb0923023e903d3ad125f1bd8044b4cad03e", + "https://deno.land/std@0.127.0/encoding/base64.ts": "784384e3cc39c59534d15f71e87f5b8da73cc5e1460417eb32ec9d62617d4f87" } diff --git a/dev_deps.js b/dev_deps.js index b95a19b..e043a3e 100644 --- a/dev_deps.js +++ b/dev_deps.js @@ -3,5 +3,5 @@ export { assertEquals, assertObjectMatch, assertThrows, -} from "https://deno.land/std@0.117.0/testing/asserts.ts"; -export { spy } from "https://deno.land/x/mock@0.12.1/mod.ts"; +} from "https://deno.land/std@0.127.0/testing/asserts.ts"; +export { spy } from "https://deno.land/x/mock@0.13.0/mod.ts"; diff --git a/dev_deps_lock.json b/dev_deps_lock.json index e848c16..fb160cf 100644 --- a/dev_deps_lock.json +++ b/dev_deps_lock.json @@ -1,11 +1,11 @@ { - "https://deno.land/std@0.115.1/async/delay.ts": "f2d8ccaa8ebc26594bd8b0989edfd8a96257a714c1dee2fb54d986e5bdd840ac", - "https://deno.land/std@0.115.1/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621", - "https://deno.land/std@0.115.1/testing/_diff.ts": "e6a10d2aca8d6c27a9c5b8a2dbbf64353874730af539707b5b39d4128140642d", - "https://deno.land/std@0.115.1/testing/asserts.ts": "a1fef0239a2c343b0baa49c77dcdd7412613c46f3aba2887c331a2d7ed1f645e", - "https://deno.land/std@0.117.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621", - "https://deno.land/std@0.117.0/testing/_diff.ts": "e6a10d2aca8d6c27a9c5b8a2dbbf64353874730af539707b5b39d4128140642d", - "https://deno.land/std@0.117.0/testing/asserts.ts": "a1fef0239a2c343b0baa49c77dcdd7412613c46f3aba2887c331a2d7ed1f645e", + "https://deno.land/std@0.125.0/async/delay.ts": "f38013c800f5129b14fdbf41fb4f4fbaf9525f69f4266946e14d0cf57c830b0c", + "https://deno.land/std@0.125.0/fmt/colors.ts": "870f10d440af4c309ab7bff97266d8ddb2c8a69039bcbd33253c8bfc14502116", + "https://deno.land/std@0.125.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", + "https://deno.land/std@0.125.0/testing/asserts.ts": "b7a2b7f80a12b9b2abe1bfeb7b84ead0e971671977c4d70390f43abd85c904ae", + "https://deno.land/std@0.127.0/fmt/colors.ts": "870f10d440af4c309ab7bff97266d8ddb2c8a69039bcbd33253c8bfc14502116", + "https://deno.land/std@0.127.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", + "https://deno.land/std@0.127.0/testing/asserts.ts": "7aa4ba645a4350040ccb3df250353c2d77dad71a582386c9be54faf1f6b0ad7f", "https://deno.land/x/collections@0.11.2/common.ts": "34e8367e3696c3f872ae417d7c421fa908a5a2125a1c4cb259f7dee9561a7096", "https://deno.land/x/collections@0.11.2/comparators.ts": "08563340dbb0051f032bacdcf854bcabd13d607d2e8cb1889826417419df89d0", "https://deno.land/x/collections@0.11.2/trees/bs_node.ts": "854d39f6d60cdcb47e1183f0fa67091e6bad59dd2b13252a8b38b1b37269fa67", @@ -14,11 +14,11 @@ "https://deno.land/x/collections@0.11.2/trees/rb_tree.ts": "e1e5f4e26bc255ebf41d72b498a2af903af69f0391276a4a0eac6d46fc43f942", "https://deno.land/x/collections@0.11.2/vector.ts": "23cb91087cc89ce9a1e10954336484b537a44bd786e21babc310ae85cb7ad52d", "https://deno.land/x/mixins@0.7.4/apply.ts": "dad7095324f5ce23693a0bc0eb3238f230c0ed2160ea8c285f3773ff7c76dcb0", - "https://deno.land/x/mock@0.12.1/asserts.ts": "6face6e51a0e7ce53c87ceec0e9ab69879c3bb6102ca69538f37b078cb86fddb", - "https://deno.land/x/mock@0.12.1/callbacks.ts": "bedb33e2831dd5bf53a15d1a95992e4eee4c0a16ee323e1077160823b11b4f7d", - "https://deno.land/x/mock@0.12.1/deps.ts": "1d7ff12aacf371d4b716d04aaf36c3fd3f0835f0e5d34bf924d38b903e7277c5", - "https://deno.land/x/mock@0.12.1/mod.ts": "ff617c0a264265c012fb79ad6c6055cc9f851e55d26dc261b64ff3a2c5976740", - "https://deno.land/x/mock@0.12.1/spy.ts": "439604d2c01bbfa9de91e2f28d07a969353c866fcec65a22d72e8c9b281107f9", - "https://deno.land/x/mock@0.12.1/stub.ts": "4cbf836fed2916d5293ac4fb8b185f5a28dd7e814beb5ea8113ea37c4e6e5593", - "https://deno.land/x/mock@0.12.1/time.ts": "d455b74a217f496ddc610922d8846da9923563ed34c6f14447075ef89530e50d" + "https://deno.land/x/mock@0.13.0/asserts.ts": "6face6e51a0e7ce53c87ceec0e9ab69879c3bb6102ca69538f37b078cb86fddb", + "https://deno.land/x/mock@0.13.0/callbacks.ts": "bedb33e2831dd5bf53a15d1a95992e4eee4c0a16ee323e1077160823b11b4f7d", + "https://deno.land/x/mock@0.13.0/deps.ts": "fa99d7aa39813faebb8df7d14d4b78049b368a6b62bc9ae489586890bbef4aad", + "https://deno.land/x/mock@0.13.0/mod.ts": "ff617c0a264265c012fb79ad6c6055cc9f851e55d26dc261b64ff3a2c5976740", + "https://deno.land/x/mock@0.13.0/spy.ts": "4f313c6b8ccf613010f9ef4f6f7b178e38bb30d12904746d3ad23da859782313", + "https://deno.land/x/mock@0.13.0/stub.ts": "4cbf836fed2916d5293ac4fb8b185f5a28dd7e814beb5ea8113ea37c4e6e5593", + "https://deno.land/x/mock@0.13.0/time.ts": "9e685cf14541abbb40818b48801973c821d0ef3e439e0afda51da6c2b5005d49" } diff --git a/scripts/lock.sh b/scripts/lock.sh new file mode 100755 index 0000000..eba8995 --- /dev/null +++ b/scripts/lock.sh @@ -0,0 +1,2 @@ +deno cache --unstable --no-check --lock=dev_deps_lock.json --lock-write dev_deps.js +deno cache --unstable --no-check --lock=deps_lock.json --lock-write deps.js From 5362aa8c440a5eb36b2caff84c89b7afee19c84d Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Tue, 1 Mar 2022 16:04:33 -0500 Subject: [PATCH 3/6] fix!: resolve hyper error and bubble rejected non-handled exceptions. #12 various bug fixes and error mappings BREAKING CHANGE - caught exceptions are resolved, not rejected, as hyper errors The only time a method on the adapter should return a rejected promise is in the case of an unhandled exception. Aligns with discussion detailed in: https://github.com/hyper63/journal/blob/master/design-docs/003-errors.md --- adapter.js | 261 ++++++++++++++++++++++++---------- adapter_test.js | 361 ++++++++++++++++++++++++++++++++++++++++++++++-- async-fetch.js | 36 ++++- utils.js | 179 ++++++++++++++++++++++++ 4 files changed, 750 insertions(+), 87 deletions(-) create mode 100644 utils.js diff --git a/adapter.js b/adapter.js index 4877647..d582f5f 100644 --- a/adapter.js +++ b/adapter.js @@ -10,8 +10,16 @@ import { removeDocPath, updateDocPath, } from "./paths.js"; +import { + esErrToHyperErr, + handleHyperErr, + moveUnderscoreId, + toEsErr, + toUnderscoreId, +} from "./utils.js"; const { + compose, set, lensProp, pluck, @@ -23,6 +31,9 @@ const { concat, flip, toPairs, + identity, + ifElse, + has, } = R; const { Async } = crocks; @@ -71,19 +82,6 @@ const { Async } = crocks; * @property {Array} matches */ -const handleRejectedResponse = (res) => - Async.of(res) - .chain(Async.fromPromise((res) => res.json())) - .bichain( - () => Async.Rejected({ ok: false, status: res.status }), // not json body, so no message - (body) => - Async.Rejected({ - ok: false, - status: res.status, - msg: JSON.stringify(body), - }), - ); - /** * TODO: * - Sanitize inputs ie. index names @@ -93,15 +91,38 @@ const handleRejectedResponse = (res) => * - ? Should we expose Elasticsearch response in result as res? */ export default function ({ config, asyncFetch, headers, handleResponse }) { + /** + * Sometimes, elasticsearch automatically creates an index + * we don't want that. So on some calls we check + */ + const checkIndexExists = (index) => + asyncFetch( + createIndexPath(config.origin, index), + { + headers, + method: "GET", + }, + ) + .chain( + handleResponse((res) => res.status < 400), + ); + /** * @param {IndexInfo} * @returns {Promise} */ function createIndex({ index, mappings }) { - const properties = mappings.fields.reduce( + let properties = mappings.fields.reduce( (a, f) => set(lensProp(f), { type: "text" }, a), {}, ); + + /** + * _id is automatically mapped, and will produce an error if included, + * so rename to id + */ + properties = moveUnderscoreId(properties); + console.log("adapter-elasticsearch", properties); return asyncFetch( @@ -115,8 +136,12 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { }, ) .chain(handleResponse((res) => res.status < 400)) + .bimap( + esErrToHyperErr({ subject: `index ${index}`, index: `index ${index}` }), + identity, + ) .bichain( - handleRejectedResponse, + handleHyperErr, always(Async.Resolved({ ok: true })), ) .toPromise(); @@ -137,8 +162,12 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { .chain( handleResponse((res) => res.status === 200), ) + .bimap( + esErrToHyperErr({ subject: `index ${index}`, index: `index ${index}` }), + identity, + ) .bichain( - handleRejectedResponse, + handleHyperErr, always(Async.Resolved({ ok: true })), ) .toPromise(); @@ -149,19 +178,39 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { * @returns {Promise} */ function indexDoc({ index, key, doc }) { - return asyncFetch( - indexDocPath(config.origin, index, key), - { - headers, - method: "PUT", - body: JSON.stringify(doc), - }, - ) + /** + * From Elasticsearch: + * Field [_id] is a metadata field and cannot be added inside a document. + * + * So we rename _id + */ + doc = moveUnderscoreId(doc); + + // check index exists + return checkIndexExists(index) + .chain(() => + // Now actually index the document + asyncFetch( + indexDocPath(config.origin, index, key), + { + headers, + method: "PUT", + body: JSON.stringify(doc), + }, + ) + ) .chain( handleResponse((res) => res.status < 400), ) + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + identity, + ) .bichain( - handleRejectedResponse, + handleHyperErr, always(Async.Resolved({ ok: true })), ) .toPromise(); @@ -182,8 +231,15 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { .chain( handleResponse((res) => res.status < 400), ) + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + toUnderscoreId, + ) .bichain( - handleRejectedResponse, + handleHyperErr, (res) => Async.Resolved({ ok: true, key, doc: res }), ) .toPromise(); @@ -194,19 +250,37 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { * @returns {Promise} */ function updateDoc({ index, key, doc }) { - return asyncFetch( - updateDocPath(config.origin, index, key), - { - headers, - method: "PUT", - body: JSON.stringify(doc), - }, - ) + /** + * From Elasticsearch: + * Field [_id] is a metadata field and cannot be added inside a document. + * + * So we move _id to a field, and map it back when the document is pulled out + */ + doc = moveUnderscoreId(doc); + + return checkIndexExists(index) + .chain(() => + asyncFetch( + updateDocPath(config.origin, index, key), + { + headers, + method: "PUT", + body: JSON.stringify(doc), + }, + ) + ) .chain( handleResponse((res) => res.status < 400), ) + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + toUnderscoreId, + ) .bichain( - handleRejectedResponse, + handleHyperErr, always(Async.Resolved({ ok: true })), ) .toPromise(); @@ -225,10 +299,18 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { }, ) .chain( - handleResponse((res) => res.status < 400), + // 404 not found, so just map to happy path + handleResponse((res) => res.status < 400 || res.status === 404), + ) + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + identity, ) .bichain( - handleRejectedResponse, + handleHyperErr, always(Async.Resolved({ ok: true })), ) .toPromise(); @@ -237,41 +319,74 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { /** * @param {BulkIndex} * @returns {Promise} - * - * TODO: maybe we could just Promise.all a map to indexDoc()? */ function bulk({ index, docs }) { - return asyncFetch( - bulkPath(config.origin), - { - headers, - method: "POST", - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk-api-example - body: pipe( - reduce( - ( - arr, - doc, - ) => [...arr, { index: { _index: index, _id: doc.id } }, doc], - [], - ), - // stringify each object in arr - map(JSON.stringify.bind(JSON)), - join("\n"), - // Bulk payload must end with a newline - flip(concat)("\n"), - )(docs), - }, - ) + return checkIndexExists(index) + .chain(() => + asyncFetch( + bulkPath(config.origin), + { + headers, + method: "POST", + // See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk-api-example + body: pipe( + reduce( + ( + arr, + doc, + ) => [ + ...arr, + { index: { _index: index, _id: doc._id || doc.id } }, + moveUnderscoreId(doc), + ], + [], + ), + // stringify each object in arr + map(JSON.stringify.bind(JSON)), + join("\n"), + // Bulk payload must end with a newline + flip(concat)("\n"), + )(docs), + }, + ) + ) .chain( handleResponse((res) => res.status < 400), ) + .bimap( + esErrToHyperErr({ + subject: `docs with ids ${pluck("id", docs).join(", ")}`, + index: `index ${index}`, + }), + identity, + ) .bichain( - handleRejectedResponse, + handleHyperErr, + /** + * bulk response could be a mixture of success and hyper errors + */ (res) => Async.Resolved({ ok: true, - results: map((o) => ({ ok: true, id: o.index._id }), res.items), + results: map( + ifElse( + has("error"), + (item) => + compose( + esErrToHyperErr({ + subject: `document at key ${item._id}`, + index: `index ${index}`, + }), + () => + toEsErr({ + ...item.error, + status: item.status, + }), + )(item), + (item) => ({ ok: true, id: item._id }), + ), + pluck("index", res.items), + ), }), ) .toPromise(); @@ -308,17 +423,23 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { }, ) .chain(handleResponse((res) => res.status < 400)) + .bimap( + esErrToHyperErr({ + subject: `query against index ${index}`, + index: `index ${index}`, + }), + identity, + ) .bichain( // query failure - handleRejectedResponse, + handleHyperErr, // Success (res) => - Async.Resolved( - { - ok: true, - matches: pluck("_source", res.hits.hits), - }, - ), + compose( + (matches) => Async.Resolved({ ok: true, matches }), + map(toUnderscoreId), + pluck("_source"), + )(res.hits.hits), ) .toPromise(); } diff --git a/adapter_test.js b/adapter_test.js index 3b5520b..42f1df3 100644 --- a/adapter_test.js +++ b/adapter_test.js @@ -12,6 +12,7 @@ import { removeDocPath, updateDocPath, } from "./paths.js"; +import { moveUnderscoreId } from "./utils.js"; const headers = createHeaders("admin", "password"); @@ -41,6 +42,10 @@ const stubResponse = (status, body) => { const fetch = spy(() => Promise.resolve(response)); +const cleanup = () => { + fetch.calls.splice(0, fetch.calls.length); +}; + const adapter = createAdapter({ config: { origin: ES }, asyncFetch: asyncFetch(fetch), @@ -54,7 +59,7 @@ Deno.test("remove index", async () => { const result = await adapter.deleteIndex(INDEX); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [deleteIndexPath(ES, INDEX), { method: "DELETE", headers, @@ -62,6 +67,26 @@ Deno.test("remove index", async () => { }); assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("remove index - not found", async () => { + // remove index + stubResponse(404, { + error: { + type: "index_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.deleteIndex(INDEX); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `index ${INDEX} not found`); + + cleanup(); }); Deno.test("create index", async () => { @@ -73,7 +98,7 @@ Deno.test("create index", async () => { mappings: { fields: ["title"] }, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [createIndexPath(ES, INDEX), { method: "PUT", headers, @@ -82,6 +107,52 @@ Deno.test("create index", async () => { }); assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("create index - maps _id", async () => { + // create index + stubResponse(201, { ok: true }); + + const result = await adapter.createIndex({ + index: INDEX, + mappings: { fields: ["_id", "title"] }, + }); + + assertObjectMatch(fetch.calls.pop(), { + args: [createIndexPath(ES, INDEX), { + method: "PUT", + headers, + body: + '{"mappings":{"properties":{"title":{"type":"text"},"__movedUnderscoreId63__":{"type":"text"}}}}', + }], + }); + + assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("create index - error", async () => { + // create index + stubResponse(404, { + error: { + type: "mapper_parsing_exception", + reason: "foo", + }, + }); + + const result = await adapter.createIndex({ + index: INDEX, + mappings: { fields: ["title"] }, + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 422); + assertEquals(result.msg, `failed to parse mapping for index ${INDEX}: foo`); + + cleanup(); }); Deno.test("index document", async () => { @@ -94,7 +165,7 @@ Deno.test("index document", async () => { doc: DOC1, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [indexDocPath(ES, INDEX, DOC1.id), { method: "PUT", headers, @@ -103,6 +174,58 @@ Deno.test("index document", async () => { }); assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("index document - error", async () => { + // index doc + stubResponse(404, { + error: { + type: "index_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.indexDoc({ + index: INDEX, + key: DOC1.id, + doc: DOC1, + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `index ${INDEX} not found`); + + cleanup(); +}); + +Deno.test("index document - maps _id", async () => { + // index doc + stubResponse(200, { ok: true }); + + const withUnderscoreId = { + ...DOC1, + _id: DOC1.id, + }; + + const result = await adapter.indexDoc({ + index: INDEX, + key: DOC1.id, + doc: withUnderscoreId, + }); + + assertObjectMatch(fetch.calls.pop(), { + args: [indexDocPath(ES, INDEX, DOC1.id), { + method: "PUT", + headers, + body: JSON.stringify(moveUnderscoreId(withUnderscoreId)), + }], + }); + + assertEquals(result.ok, true); + + cleanup(); }); Deno.test("get document", async () => { @@ -114,7 +237,7 @@ Deno.test("get document", async () => { key: DOC1.id, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [getDocPath(ES, INDEX, DOC1.id), { method: "GET", headers, @@ -123,6 +246,50 @@ Deno.test("get document", async () => { assertEquals(result.doc.title, DOC1.title); assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("get document - error doc not found", async () => { + // get doc + stubResponse(404, { + error: { + type: "resource_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.getDoc({ + index: INDEX, + key: DOC1.id, + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `document at key ${DOC1.id} not found`); + + cleanup(); +}); + +Deno.test("get document - error index not found", async () => { + // get doc + stubResponse(404, { + error: { + type: "index_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.getDoc({ + index: INDEX, + key: DOC1.id, + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `index ${INDEX} not found`); + + cleanup(); }); Deno.test("update document", async () => { @@ -138,7 +305,7 @@ Deno.test("update document", async () => { }, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [updateDocPath(ES, INDEX, DOC1.id), { method: "PUT", headers, @@ -150,8 +317,12 @@ Deno.test("update document", async () => { }); assertEquals(result.ok, true); + + cleanup(); }); +// update document sad paths covered by other tests + Deno.test("delete document", async () => { // remove doc stubResponse(201, { ok: true }); @@ -161,7 +332,7 @@ Deno.test("delete document", async () => { key: DOC1.id, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [removeDocPath(ES, INDEX, DOC1.id), { method: "DELETE", headers, @@ -169,6 +340,29 @@ Deno.test("delete document", async () => { }); assertEquals(result.ok, true); + + cleanup(); +}); + +Deno.test("delete document - 404 passthrough", async () => { + // remove doc + stubResponse(404, { ok: true }); + + const result = await adapter.removeDoc({ + index: INDEX, + key: "not_found", + }); + + assertObjectMatch(fetch.calls.pop(), { + args: [removeDocPath(ES, INDEX, "not_found"), { + method: "DELETE", + headers, + }], + }); + + assertEquals(result.ok, true); + + cleanup(); }); Deno.test("bulk", async () => { @@ -188,7 +382,7 @@ Deno.test("bulk", async () => { ], }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [bulkPath(ES), { method: "POST", headers, @@ -198,6 +392,119 @@ Deno.test("bulk", async () => { assertEquals(result.ok, true); assert(result.results); + + cleanup(); +}); + +Deno.test("bulk - maps all _ids and operations", async () => { + // bulk operation + stubResponse(200, { + items: [ + { index: { _id: DOC1.id, ...DOC1 } }, + { index: { _id: DOC2.id, ...DOC2 } }, + ], + }); + + const doc1WithUnderscoreId = { ...DOC1, _id: DOC1.id }; + await adapter.bulk({ + index: INDEX, + docs: [ + doc1WithUnderscoreId, + DOC2, + ], + }); + + const call = fetch.calls.pop(); + assertObjectMatch(call, { + args: [bulkPath(ES), { + method: "POST", + headers, + }], + }); + + let { args: [, { body }] } = call; + + body = body.split("\n"); + console.log(body); + body = body.slice(0, -1); + const [index1, first, index2] = body; + + assertEquals(doc1WithUnderscoreId._id, JSON.parse(index1).index._id); + assertEquals(DOC2.id, JSON.parse(index2).index._id); + + assertEquals(first, JSON.stringify(moveUnderscoreId(doc1WithUnderscoreId))); + + cleanup(); +}); + +Deno.test("bulk - entire request error", async () => { + // bulk operation + stubResponse(404, { + error: { + type: "index_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.bulk({ + index: INDEX, + docs: [ + DOC1, + DOC2, + ], + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `index ${INDEX} not found`); + + cleanup(); +}); + +Deno.test("bulk - with errors", async () => { + // bulk operation + stubResponse(200, { + items: [ + { index: { _id: DOC1.id, ...DOC1 } }, + { + index: { + _id: DOC2.id, + ...DOC2, + status: 400, + error: { type: "resource_already_exists_exception", reason: "foo" }, + }, + }, + ], + }); + + const result = await adapter.bulk({ + index: INDEX, + docs: [ + DOC1, + DOC2, + ], + }); + + assertObjectMatch(fetch.calls.pop(), { + args: [bulkPath(ES), { + method: "POST", + headers, + // TODO: Tyler. Assert body here eventually + }], + }); + + assertEquals(result.ok, true); + assert(result.results); + const [success, err] = result.results; + + assert(success.ok); + assert(success.id); + + assertEquals(err.status, 409); + assertEquals(err.msg, `document at key ${DOC2.id} already exists`); + assertEquals(err.ok, false); + + cleanup(); }); Deno.test("query", async () => { @@ -205,7 +512,8 @@ Deno.test("query", async () => { stubResponse(200, { hits: { hits: [ - DOC1, + { _source: DOC1 }, + { _source: moveUnderscoreId({ ...DOC2, _id: DOC2.id }) }, ], }, }); @@ -221,7 +529,7 @@ Deno.test("query", async () => { }, }); - assertObjectMatch(fetch.calls.shift(), { + assertObjectMatch(fetch.calls.pop(), { args: [queryPath(ES, INDEX), { method: "POST", headers, @@ -231,5 +539,38 @@ Deno.test("query", async () => { assertEquals(result.ok, true); assert(result.matches); - assertEquals(result.matches.length, 1); + assertEquals(result.matches.length, 2); + + assertEquals(result.matches[0].id, DOC1.id); + // maps _id back + assertEquals(result.matches[1]._id, DOC2.id); + + cleanup(); +}); + +Deno.test("query - error", async () => { + // query docs + stubResponse(404, { + error: { + type: "index_not_found_exception", + reason: "foo", + }, + }); + + const result = await adapter.query({ + index: "movies", + q: { + query: "gatsby", + fields: ["title"], + filter: { + rating: 4, + }, + }, + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 404); + assertEquals(result.msg, `index ${INDEX} not found`); + + cleanup(); }); diff --git a/async-fetch.js b/async-fetch.js index 1feef39..71d4388 100644 --- a/async-fetch.js +++ b/async-fetch.js @@ -1,12 +1,21 @@ import { base64Encode, crocks, R } from "./deps.js"; +import { toEsErr } from "./utils.js"; const { Async } = crocks; -const { ifElse, assoc, pipe, identity } = R; +const { + ifElse, + assoc, + pipe, + identity, + propOr, + compose, + tap, +} = R; // TODO: Tyler. wrap with opionated approach like before with https://github.com/vercel/fetch -const asyncFetch = (fetch) => Async.fromPromise(fetch); +export const asyncFetch = (fetch) => Async.fromPromise(fetch); -const createHeaders = (username, password) => +export const createHeaders = (username, password) => pipe( assoc("Content-Type", "application/json"), assoc("Accept", "application/json"), @@ -22,13 +31,26 @@ const createHeaders = (username, password) => ), )({}); -const handleResponse = (pred) => +export const handleResponse = (pred) => ifElse( (res) => pred(res), (res) => Async.of(res) .chain(Async.fromPromise((res) => res.json())), - (res) => Async.Rejected(res), + (res) => + Async.of(res) + .chain(Async.fromPromise((res) => res.json())) + .map(tap(console.log)) + /** + * Elasticsearch errors have the format: + * { error: { reason: string }, status: number } + */ + .map((body) => + compose( + (err) => toEsErr(err, res.status), + assoc("status", body.status), + propOr(body, "error"), + )(body) + ) + .chain(Async.Rejected), ); - -export { asyncFetch, createHeaders, handleResponse }; diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..9b015ce --- /dev/null +++ b/utils.js @@ -0,0 +1,179 @@ +import { crocks, R } from "./deps.js"; + +const { Async } = crocks; +const { + __, + assoc, + dissoc, + isEmpty, + ifElse, + defaultTo, + propEq, + cond, + is, + identity, + T, + complement, + isNil, + compose, + has, + allPass, + anyPass, + filter, + evolve, + applyTo, + propOr, + always, + path, +} = R; + +const isDefined = complement(isNil); +const isEmptyObject = allPass([ + complement(is(Array)), // not an array + is(Object), + isEmpty, +]); +const rejectNil = filter(isDefined); + +/** + * Constructs a hyper-esque error + * + * @typedef {Object} HyperErrArgs + * @property {string} msg + * @property {string?} status + * + * @typedef {Object} NotOk + * @property {false} ok + * + * @param {(HyperErrArgs | string)} argsOrMsg + * @returns {NotOk & HyperErrArgs} - the hyper-esque error + */ +export const HyperErr = (argsOrMsg) => + compose( + ({ ok, msg, status }) => rejectNil({ ok, msg, status }), // pick and filter nil + assoc("ok", false), + cond([ + // string + [is(String), assoc("msg", __, {})], + // { msg?, status? } + [ + anyPass([ + isEmptyObject, + has("msg"), + has("status"), + ]), + identity, + ], + // Fallthrough to error + [T, () => { + throw new Error( + "HyperErr args must be a string or an object with msg or status", + ); + }], + ]), + defaultTo({}), + )(argsOrMsg); + +export const isHyperErr = allPass([ + propEq("ok", false), + /** + * should not have an _id. + * Otherwise it's a document ie data.retrieveDocument + * or cache.getDoc + */ + complement(has("_id")), +]); + +export const handleHyperErr = ifElse( + isHyperErr, + Async.Resolved, + Async.Rejected, +); + +export const toEsErr = (err, fallbackStatus = 500) => ({ + err, // backreference + /** + * used caused_by + * fallback to top-lvl reason + * fallback to generic error message + */ + reason: compose( + defaultTo(propOr("an error occurred", "reason", err)), + path(["caused_by", "reason"]), + )(err), + type: propOr("unknown", "type", err), + /** + * use body status + * fallback to response status + */ + status: propOr(fallbackStatus, "status", err), +}); + +/** + * Generate string templates + */ +const template = (strings, ...keys) => + (dict) => { + const result = [strings[0]]; + keys.forEach((key, i) => { + result.push(dict[key], strings[i + 1]); + }); + return result.join(""); + }; + +export const esErrToHyperErr = (context) => + compose( + HyperErr, + ({ status, type, reason }) => + evolve( + { msg: applyTo({ reason, ...context }) }, // populate the msg template + /** + * Set status and msg template + */ + propOr( + { status, msg: always(reason) }, + type, + { + resource_already_exists_exception: { + status: 409, + msg: template`${"subject"} already exists`, + }, + mapper_parsing_exception: { + status: 422, + msg: template + `failed to parse mapping for ${"subject"}: ${"reason"}`, + }, + index_not_found_exception: { + status: 404, + msg: template`${"index"} not found`, + }, + resource_not_found_exception: { + status: 404, + msg: template`${"subject"} not found`, + }, + not_found: { + status: 404, + msg: template`${"subject"} not found`, + }, + }, + ), + ), + ); + +const swap = (old, cur) => + compose( + dissoc(old), + (o) => assoc(cur, o[old], o), + ); + +export const moveUnderscoreId = ifElse( + has("_id"), + swap("_id", "__movedUnderscoreId63__"), + identity, +); + +export const toUnderscoreId = ifElse( + has("__movedUnderscoreId63__"), + swap("__movedUnderscoreId63__", "_id"), + identity, +); From dc8ae03e239879f482cce83cf1b1625b6f174e38 Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Tue, 1 Mar 2022 16:05:16 -0500 Subject: [PATCH 4/6] chore: bump test harness --- scripts/hyper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/hyper.js b/scripts/hyper.js index ddda314..8bee2cd 100644 --- a/scripts/hyper.js +++ b/scripts/hyper.js @@ -1,5 +1,5 @@ -import hyper from "https://x.nest.land/hyper@2.0.0/mod.js"; -import app from "https://x.nest.land/hyper-app-opine@1.2.7/mod.js"; +import hyper from "https://x.nest.land/hyper@3.0.0/mod.js"; +import app from "https://x.nest.land/hyper-app-opine@2.0.0/mod.js"; import search from "../mod.js"; From f41c09c8b537d73fcede18a734a0e13f3b96fa94 Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Wed, 2 Mar 2022 11:36:14 -0500 Subject: [PATCH 5/6] fix: ensure each document in bulk has identifier. Cleanup --- adapter.js | 142 ++++++++++++++++++------------------------------ adapter_test.js | 20 ++++++- utils.js | 76 ++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 89 deletions(-) diff --git a/adapter.js b/adapter.js index d582f5f..67f9e48 100644 --- a/adapter.js +++ b/adapter.js @@ -11,29 +11,26 @@ import { updateDocPath, } from "./paths.js"; import { + bulkToEsBulk, esErrToHyperErr, handleHyperErr, + HyperErr, + mappingsToEsMappings, moveUnderscoreId, + queryToEsQuery, toEsErr, toUnderscoreId, } from "./utils.js"; const { compose, - set, - lensProp, pluck, - reduce, always, - pipe, map, - join, - concat, - flip, - toPairs, identity, ifElse, has, + anyPass, } = R; const { Async } = crocks; @@ -105,6 +102,9 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { ) .chain( handleResponse((res) => res.status < 400), + ).bimap( + esErrToHyperErr({ subject: `index ${index}`, index: `index ${index}` }), + identity, ); /** @@ -112,27 +112,16 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { * @returns {Promise} */ function createIndex({ index, mappings }) { - let properties = mappings.fields.reduce( - (a, f) => set(lensProp(f), { type: "text" }, a), - {}, - ); - - /** - * _id is automatically mapped, and will produce an error if included, - * so rename to id - */ - properties = moveUnderscoreId(properties); + mappings = mappingsToEsMappings(mappings); - console.log("adapter-elasticsearch", properties); + console.log("adapter-elasticsearch", mappings); return asyncFetch( createIndexPath(config.origin, index), { headers, method: "PUT", - body: JSON.stringify({ - mappings: { properties }, - }), + body: JSON.stringify(mappings), }, ) .chain(handleResponse((res) => res.status < 400)) @@ -197,17 +186,16 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { method: "PUT", body: JSON.stringify(doc), }, + ).chain( + handleResponse((res) => res.status < 400), ) - ) - .chain( - handleResponse((res) => res.status < 400), - ) - .bimap( - esErrToHyperErr({ - subject: `document at key ${key}`, - index: `index ${index}`, - }), - identity, + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + identity, + ) ) .bichain( handleHyperErr, @@ -267,17 +255,16 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { method: "PUT", body: JSON.stringify(doc), }, + ).chain( + handleResponse((res) => res.status < 400), ) - ) - .chain( - handleResponse((res) => res.status < 400), - ) - .bimap( - esErrToHyperErr({ - subject: `document at key ${key}`, - index: `index ${index}`, - }), - toUnderscoreId, + .bimap( + esErrToHyperErr({ + subject: `document at key ${key}`, + index: `index ${index}`, + }), + toUnderscoreId, + ) ) .bichain( handleHyperErr, @@ -322,43 +309,38 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { */ function bulk({ index, docs }) { return checkIndexExists(index) + // check each document has an id or _id field + .chain(() => { + return docs.filter(anyPass([ + has("id"), + has("_id"), + ])).length === docs.length + ? Async.Resolved() + : Async.Rejected( + HyperErr({ + status: 422, + msg: "Each document must have an id or _id field", + }), + ); + }) .chain(() => asyncFetch( bulkPath(config.origin), { headers, method: "POST", - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk-api-example - body: pipe( - reduce( - ( - arr, - doc, - ) => [ - ...arr, - { index: { _index: index, _id: doc._id || doc.id } }, - moveUnderscoreId(doc), - ], - [], - ), - // stringify each object in arr - map(JSON.stringify.bind(JSON)), - join("\n"), - // Bulk payload must end with a newline - flip(concat)("\n"), - )(docs), + body: bulkToEsBulk(index, docs), }, + ).chain( + handleResponse((res) => res.status < 400), ) - ) - .chain( - handleResponse((res) => res.status < 400), - ) - .bimap( - esErrToHyperErr({ - subject: `docs with ids ${pluck("id", docs).join(", ")}`, - index: `index ${index}`, - }), - identity, + .bimap( + esErrToHyperErr({ + subject: `docs with ids ${pluck("id", docs).join(", ")}`, + index: `index ${index}`, + }), + identity, + ) ) .bichain( handleHyperErr, @@ -403,23 +385,7 @@ export default function ({ config, asyncFetch, headers, handleResponse }) { headers, method: "POST", // anything undefined will not be stringified, so this shorthand works - body: JSON.stringify({ - query: { - bool: { - must: { - multi_match: { - query, - // See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-fuzziness - fuzziness: "AUTO", - fields, - }, - }, - filter: toPairs(filter).map( - ([key, value]) => ({ term: { [key]: value } }), - ), - }, - }, - }), + body: JSON.stringify(queryToEsQuery({ query, fields, filter })), }, ) .chain(handleResponse((res) => res.status < 400)) diff --git a/adapter_test.js b/adapter_test.js index 42f1df3..c9bdc4c 100644 --- a/adapter_test.js +++ b/adapter_test.js @@ -44,6 +44,7 @@ const fetch = spy(() => Promise.resolve(response)); const cleanup = () => { fetch.calls.splice(0, fetch.calls.length); + stubResponse(200, { ok: true }); }; const adapter = createAdapter({ @@ -426,7 +427,7 @@ Deno.test("bulk - maps all _ids and operations", async () => { body = body.split("\n"); console.log(body); - body = body.slice(0, -1); + body = body.slice(0, -1); // remove \n from end of list const [index1, first, index2] = body; assertEquals(doc1WithUnderscoreId._id, JSON.parse(index1).index._id); @@ -461,6 +462,23 @@ Deno.test("bulk - entire request error", async () => { cleanup(); }); +Deno.test("bulk - check each doc has an id or _id", async () => { + const result = await adapter.bulk({ + index: INDEX, + docs: [ + { _id: DOC1.id, ...DOC1 }, + DOC2, + { no_id: "foo", fizz: "buzz" }, + ], + }); + + assertEquals(result.ok, false); + assertEquals(result.status, 422); + assertEquals(result.msg, "Each document must have an id or _id field"); + + cleanup(); +}); + Deno.test("bulk - with errors", async () => { // bulk operation stubResponse(200, { diff --git a/utils.js b/utils.js index 9b015ce..d7671db 100644 --- a/utils.js +++ b/utils.js @@ -25,6 +25,14 @@ const { propOr, always, path, + flip, + join, + reduce, + map, + concat, + set, + lensProp, + toPairs, } = R; const isDefined = complement(isNil); @@ -177,3 +185,71 @@ export const toUnderscoreId = ifElse( swap("__movedUnderscoreId63__", "_id"), identity, ); + +/** + * Create an elasticsearch _bulk index payload + * + * See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk-api-example + * + * @param {string} index + * @param {object[]} docs + * @returns {string} - the bulk payload to send to elasticsearch + */ +export const bulkToEsBulk = (index, docs) => + compose( + // Bulk payload must end with a newline + flip(concat)("\n"), + join("\n"), + // stringify each object in arr + map(JSON.stringify.bind(JSON)), + reduce( + ( + arr, + doc, + ) => [ + ...arr, + { index: { _index: index, _id: doc._id || doc.id } }, + moveUnderscoreId(doc), + ], + [], + ), + )(docs); + +/** + * @param {mappings} - hyper index mappings { fields } + * @returns {object} - + */ +export const mappingsToEsMappings = compose( + (properties) => ({ + mappings: { properties }, + }), + /** + * _id is automatically mapped, and will produce an error if included, + * so rename id + */ + moveUnderscoreId, + (mappings) => + mappings.fields.reduce( + (a, f) => set(lensProp(f), { type: "text" }, a), + {}, + ), + defaultTo({ fields: [] }), +); + +export const queryToEsQuery = ({ query, fields, filter }) => ({ + query: { + bool: { + must: { + multi_match: { + query, + // See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-fuzziness + fuzziness: "AUTO", + fields, + }, + }, + filter: toPairs(filter).map( + ([key, value]) => ({ term: { [key]: value } }), + ), + }, + }, +}); From b3c60567ac6db97f07530fbf06c2c4ee27338bbf Mon Sep 17 00:00:00 2001 From: Tyler Hall Date: Wed, 2 Mar 2022 12:09:02 -0500 Subject: [PATCH 6/6] fix: map query to and from _id alias --- adapter.js | 9 ++++----- adapter_test.js | 32 +++++++++++++++++++++++++++----- utils.js | 19 ++++++++++++++----- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/adapter.js b/adapter.js index 67f9e48..97672da 100644 --- a/adapter.js +++ b/adapter.js @@ -55,14 +55,13 @@ const { Async } = crocks; * @property {string} key * * @typedef {Object} SearchOptions - * @property {Array} fields - * @property {Object} boost - * @property {boolean} prefix + * @property {string} query + * @property {Array} [fields] + * @property {Object} [filter] * * @typedef {Object} SearchQuery * @property {string} index - * @property {string} query - * @property {SearchOptions} [options] + * @property {SearchOptions} q * * @typedef {Object} Response * @property {boolean} ok diff --git a/adapter_test.js b/adapter_test.js index c9bdc4c..04b2d13 100644 --- a/adapter_test.js +++ b/adapter_test.js @@ -12,7 +12,7 @@ import { removeDocPath, updateDocPath, } from "./paths.js"; -import { moveUnderscoreId } from "./utils.js"; +import { moveUnderscoreId, underscoreIdAlias } from "./utils.js"; const headers = createHeaders("admin", "password"); @@ -126,7 +126,7 @@ Deno.test("create index - maps _id", async () => { method: "PUT", headers, body: - '{"mappings":{"properties":{"title":{"type":"text"},"__movedUnderscoreId63__":{"type":"text"}}}}', + `{"mappings":{"properties":{"title":{"type":"text"},"${underscoreIdAlias}":{"type":"text"}}}}`, }], }); @@ -540,21 +540,43 @@ Deno.test("query", async () => { index: "movies", q: { query: "gatsby", - fields: ["title"], + fields: ["title", "_id"], filter: { rating: 4, + _id: "id-1", }, }, }); - assertObjectMatch(fetch.calls.pop(), { + const call = fetch.calls.pop(); + assertObjectMatch(call, { args: [queryPath(ES, INDEX), { method: "POST", headers, - // TODO: Tyler. Assert body here eventually }], }); + let { args: [, { body }] } = call; + body = JSON.parse(body); + + assertObjectMatch(body, { + query: { + bool: { + must: { + multi_match: { + query: "gatsby", + fuzziness: "AUTO", + fields: ["title", underscoreIdAlias], + }, + }, + filter: [ + { term: { rating: 4 } }, + { term: { [underscoreIdAlias]: "id-1" } }, + ], + }, + }, + }); + assertEquals(result.ok, true); assert(result.matches); assertEquals(result.matches.length, 2); diff --git a/utils.js b/utils.js index d7671db..1f03e5b 100644 --- a/utils.js +++ b/utils.js @@ -35,6 +35,8 @@ const { toPairs, } = R; +export const underscoreIdAlias = "__movedUnderscoreId63__"; + const isDefined = complement(isNil); const isEmptyObject = allPass([ complement(is(Array)), // not an array @@ -176,13 +178,13 @@ const swap = (old, cur) => export const moveUnderscoreId = ifElse( has("_id"), - swap("_id", "__movedUnderscoreId63__"), + swap("_id", underscoreIdAlias), identity, ); export const toUnderscoreId = ifElse( - has("__movedUnderscoreId63__"), - swap("__movedUnderscoreId63__", "_id"), + has(underscoreIdAlias), + swap(underscoreIdAlias, "_id"), identity, ); @@ -244,11 +246,18 @@ export const queryToEsQuery = ({ query, fields, filter }) => ({ query, // See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html#query-dsl-match-query-fuzziness fuzziness: "AUTO", - fields, + // map _id => underscoreIdAlias + fields: fields + ? fields.map((field) => field === "_id" ? underscoreIdAlias : field) + : undefined, }, }, filter: toPairs(filter).map( - ([key, value]) => ({ term: { [key]: value } }), + // map _id => underscoreIdAlias + ([key, value]) => + key === "_id" + ? ({ term: { [underscoreIdAlias]: value } }) + : ({ term: { [key]: value } }), ), }, },