run npm install to generate a package lock
This commit is contained in:
1
node_modules/.bin/marked
generated
vendored
Symbolic link
1
node_modules/.bin/marked
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../marked/bin/marked.js
|
||||
1
node_modules/.bin/ori
generated
vendored
Symbolic link
1
node_modules/.bin/ori
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../@weborigami/origami/src/cli/cli.js
|
||||
1
node_modules/.bin/semver
generated
vendored
Symbolic link
1
node_modules/.bin/semver
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../semver/bin/semver.js
|
||||
1
node_modules/.bin/smartypants
generated
vendored
Symbolic link
1
node_modules/.bin/smartypants
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../smartypants/bin/smartypants.js
|
||||
1
node_modules/.bin/smartypantsu
generated
vendored
Symbolic link
1
node_modules/.bin/smartypantsu
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../smartypants/bin/smartypantsu.js
|
||||
1
node_modules/.bin/yaml
generated
vendored
Symbolic link
1
node_modules/.bin/yaml
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../yaml/bin.mjs
|
||||
361
node_modules/.package-lock.json
generated
vendored
Normal file
361
node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,361 @@
|
||||
{
|
||||
"name": "sashinexists",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@weborigami/async-tree": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@weborigami/async-tree/-/async-tree-0.2.1.tgz",
|
||||
"integrity": "sha512-RH+RCOkT6Oy8GtSKsGUs87izjUTetq8fJ64c7LrqsssEsgMi/ENMdnnO4kVHPW4mvqSMUQqoK0WUyP9hjSrhVQ==",
|
||||
"dependencies": {
|
||||
"@weborigami/types": "0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@weborigami/language": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@weborigami/language/-/language-0.2.1.tgz",
|
||||
"integrity": "sha512-syp5L5kHdt4dW4nkjTQa2VMBS94qn+DA524wWY0XpbpyC5h1YMA3s+eOQ29PhdiB9n2xjRhu+ZjxDuLhsv2YaQ==",
|
||||
"dependencies": {
|
||||
"@weborigami/async-tree": "0.2.1",
|
||||
"@weborigami/types": "0.2.1",
|
||||
"watcher": "2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@weborigami/origami": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@weborigami/origami/-/origami-0.2.1.tgz",
|
||||
"integrity": "sha512-nKJWvasGw469qnC1KOF2uayhjlWdOBXo/NRmp3Z64jDYiKYQ60o9uH0CY3b8wB33mRvu3YREoajFNlVcwvnyqQ==",
|
||||
"dependencies": {
|
||||
"@weborigami/async-tree": "0.2.1",
|
||||
"@weborigami/language": "0.2.1",
|
||||
"@weborigami/types": "0.2.1",
|
||||
"exif-parser": "0.1.12",
|
||||
"graphviz-wasm": "3.0.2",
|
||||
"highlight.js": "11.10.0",
|
||||
"marked": "14.1.2",
|
||||
"marked-gfm-heading-id": "4.1.0",
|
||||
"marked-highlight": "2.1.4",
|
||||
"marked-smartypants": "1.1.8",
|
||||
"sharp": "0.33.5",
|
||||
"yaml": "2.5.1"
|
||||
},
|
||||
"bin": {
|
||||
"ori": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@weborigami/types": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@weborigami/types/-/types-0.2.1.tgz",
|
||||
"integrity": "sha512-+386SPOxoo1Whx3Ga+5XanXVhAz/IdwfFuFGSVhO39qj46FwG4ZEU6qqTRZpNW5tX3ABkO9DvAjqoCmapDxaXQ=="
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-base64": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/decode-base64/-/decode-base64-3.0.1.tgz",
|
||||
"integrity": "sha512-IWgiXlMAdm9c4RrOnvkFxYpfZRlOys4Wxnc/QT72hVLUZKCr7RPkfamgn2GXysCo06Zd4TGZyKaPHO4soBgSAg==",
|
||||
"dependencies": {
|
||||
"node-buffer-encoding": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dettle": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dettle/-/dettle-1.0.4.tgz",
|
||||
"integrity": "sha512-ktaWiLYYc/ajSa819+HxwABpqtk3dGIAmo5CbHvT3B6XyQSM7VNGDvCPNu94Ptc+Ti4tjTvAKRUCXU/lrVG4WQ=="
|
||||
},
|
||||
"node_modules/exif-parser": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
|
||||
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
|
||||
},
|
||||
"node_modules/function-once": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/function-once/-/function-once-3.0.0.tgz",
|
||||
"integrity": "sha512-WEhgu9PE55sHFf+SBg3lI8+CWpsqReLcsp3g12XhwSJJgnodpSpHk6StvpeVcKuHAFCAdttLrslJRFDSdLDf4g=="
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/graphviz-wasm": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/graphviz-wasm/-/graphviz-wasm-3.0.2.tgz",
|
||||
"integrity": "sha512-aaO9dxQ7LTk9zvelCajRRSdSB+ZLniL5DNSkuLXAfIAbVvYCqlLmxz4zWC2LBbNNT+w6vp8Du8SvAVxH1bSVBQ==",
|
||||
"dependencies": {
|
||||
"decode-base64": "^3.0.1",
|
||||
"function-once": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.10.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz",
|
||||
"integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz",
|
||||
"integrity": "sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/marked-gfm-heading-id": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/marked-gfm-heading-id/-/marked-gfm-heading-id-4.1.0.tgz",
|
||||
"integrity": "sha512-xRvV65Fnpq1krNspnyGsBvP0Y6h7/FrJ6U6y4e6zCWffiC1KxFFxFUKVu8ufMHop2xdvpwyWj5jPeA5W5x/6Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"github-slugger": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"marked": ">=13 <15"
|
||||
}
|
||||
},
|
||||
"node_modules/marked-highlight": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/marked-highlight/-/marked-highlight-2.1.4.tgz",
|
||||
"integrity": "sha512-D1GOkcdzP+1dzjoColL7umojefFrASDuLeyaHS0Zr/Uo9jkr1V6vpLRCzfi1djmEaWyK0SYMFtHnpkZ+cwFT1w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"marked": ">=4 <15"
|
||||
}
|
||||
},
|
||||
"node_modules/marked-smartypants": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/marked-smartypants/-/marked-smartypants-1.1.8.tgz",
|
||||
"integrity": "sha512-2n8oSjL2gSkH6M0dSdRIyLgqqky03iKQkdmoaylmIzwIhYTW204S7ry6zP2iqwSl0zSlJH2xmWgxlZ/4XB1CdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"smartypants": "^0.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"marked": ">=4 <15"
|
||||
}
|
||||
},
|
||||
"node_modules/node-buffer-encoding": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/node-buffer-encoding/-/node-buffer-encoding-1.0.2.tgz",
|
||||
"integrity": "sha512-v2QFjf04xWb5Q7cyzbi8qEwe2vw2xJBXT7+pMOLA02+KJZlcJ/6syFYiH96ClXKfOG/kyBeysAuewJ7zfAUYKQ=="
|
||||
},
|
||||
"node_modules/promise-make-counter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/promise-make-counter/-/promise-make-counter-1.0.1.tgz",
|
||||
"integrity": "sha512-R1JGFIgSJDpNV/JXxytAx6K79noEpcBiZXWDa3ic9WEMpBZbUdVVQjlA266SCicJ9CGqd70iGbbzbjRKbGU1Jg==",
|
||||
"dependencies": {
|
||||
"promise-make-naked": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-make-naked": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/promise-make-naked/-/promise-make-naked-3.0.0.tgz",
|
||||
"integrity": "sha512-h71wwAMB2udFnlPmcxQMqKl6CckNLVKdk/ROtFivE6/VmW+rQKV0DWlGJ6VphRIoq22Tkonvdi3F+jlm5XDlow=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/smartypants": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/smartypants/-/smartypants-0.2.2.tgz",
|
||||
"integrity": "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"bin": {
|
||||
"smartypants": "bin/smartypants.js",
|
||||
"smartypantsu": "bin/smartypantsu.js"
|
||||
}
|
||||
},
|
||||
"node_modules/stubborn-fs": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz",
|
||||
"integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g=="
|
||||
},
|
||||
"node_modules/tiny-readdir": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-readdir/-/tiny-readdir-2.7.3.tgz",
|
||||
"integrity": "sha512-ae1CPk7/MRhdaSIfjytuCoCjcykCNfSH36MsD2Qq8A27apaVUV0nthOcCEjiBTTloBObq2ffvm0BycUayMWh3A==",
|
||||
"dependencies": {
|
||||
"promise-make-counter": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/watcher": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/watcher/-/watcher-2.3.1.tgz",
|
||||
"integrity": "sha512-d3yl+ey35h05r5EFP0TafE2jsmQUJ9cc2aernRVyAkZiu0J3+3TbNugNcqdUJDoWOfL2p+bNsN427stsBC/HnA==",
|
||||
"dependencies": {
|
||||
"dettle": "^1.0.2",
|
||||
"stubborn-fs": "^1.2.5",
|
||||
"tiny-readdir": "^2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
node_modules/@img/sharp-libvips-linux-x64/README.md
generated
vendored
Normal file
46
node_modules/@img/sharp-libvips-linux-x64/README.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# `@img/sharp-libvips-linux-x64`
|
||||
|
||||
Prebuilt libvips and dependencies for use with sharp on Linux (glibc) x64.
|
||||
|
||||
## Licensing
|
||||
|
||||
This software contains third-party libraries
|
||||
used under the terms of the following licences:
|
||||
|
||||
| Library | Used under the terms of |
|
||||
|---------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| aom | BSD 2-Clause + [Alliance for Open Media Patent License 1.0](https://aomedia.org/license/patent-license/) |
|
||||
| cairo | Mozilla Public License 2.0 |
|
||||
| cgif | MIT Licence |
|
||||
| expat | MIT Licence |
|
||||
| fontconfig | [fontconfig Licence](https://gitlab.freedesktop.org/fontconfig/fontconfig/blob/main/COPYING) (BSD-like) |
|
||||
| freetype | [freetype Licence](https://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
|
||||
| fribidi | LGPLv3 |
|
||||
| glib | LGPLv3 |
|
||||
| harfbuzz | MIT Licence |
|
||||
| highway | Apache-2.0 License, BSD 3-Clause |
|
||||
| lcms | MIT Licence |
|
||||
| libarchive | BSD 2-Clause |
|
||||
| libexif | LGPLv3 |
|
||||
| libffi | MIT Licence |
|
||||
| libheif | LGPLv3 |
|
||||
| libimagequant | [BSD 2-Clause](https://github.com/lovell/libimagequant/blob/main/COPYRIGHT) |
|
||||
| libnsgif | MIT Licence |
|
||||
| libpng | [libpng License](https://github.com/pnggroup/libpng/blob/master/LICENSE) |
|
||||
| librsvg | LGPLv3 |
|
||||
| libspng | [BSD 2-Clause, libpng License](https://github.com/randy408/libspng/blob/master/LICENSE) |
|
||||
| libtiff | [libtiff License](https://gitlab.com/libtiff/libtiff/blob/master/LICENSE.md) (BSD-like) |
|
||||
| libvips | LGPLv3 |
|
||||
| libwebp | New BSD License |
|
||||
| libxml2 | MIT Licence |
|
||||
| mozjpeg | [zlib License, IJG License, BSD-3-Clause](https://github.com/mozilla/mozjpeg/blob/master/LICENSE.md) |
|
||||
| pango | LGPLv3 |
|
||||
| pixman | MIT Licence |
|
||||
| proxy-libintl | LGPLv3 |
|
||||
| zlib-ng | [zlib Licence](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md) |
|
||||
|
||||
Use of libraries under the terms of the LGPLv3 is via the
|
||||
"any later version" clause of the LGPLv2 or LGPLv2.1.
|
||||
|
||||
Please report any errors or omissions via
|
||||
https://github.com/lovell/sharp-libvips/issues/new
|
||||
221
node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h
generated
vendored
Normal file
221
node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
/* glibconfig.h
|
||||
*
|
||||
* This is a generated file. Please modify 'glibconfig.h.in'
|
||||
*/
|
||||
|
||||
#ifndef __GLIBCONFIG_H__
|
||||
#define __GLIBCONFIG_H__
|
||||
|
||||
#include <glib/gmacros.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <float.h>
|
||||
#define GLIB_HAVE_ALLOCA_H
|
||||
|
||||
#define GLIB_STATIC_COMPILATION 1
|
||||
#define GOBJECT_STATIC_COMPILATION 1
|
||||
#define GIO_STATIC_COMPILATION 1
|
||||
#define GMODULE_STATIC_COMPILATION 1
|
||||
#define GI_STATIC_COMPILATION 1
|
||||
#define G_INTL_STATIC_COMPILATION 1
|
||||
#define FFI_STATIC_BUILD 1
|
||||
|
||||
/* Specifies that GLib's g_print*() functions wrap the
|
||||
* system printf functions. This is useful to know, for example,
|
||||
* when using glibc's register_printf_function().
|
||||
*/
|
||||
#define GLIB_USING_SYSTEM_PRINTF
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_MINFLOAT FLT_MIN
|
||||
#define G_MAXFLOAT FLT_MAX
|
||||
#define G_MINDOUBLE DBL_MIN
|
||||
#define G_MAXDOUBLE DBL_MAX
|
||||
#define G_MINSHORT SHRT_MIN
|
||||
#define G_MAXSHORT SHRT_MAX
|
||||
#define G_MAXUSHORT USHRT_MAX
|
||||
#define G_MININT INT_MIN
|
||||
#define G_MAXINT INT_MAX
|
||||
#define G_MAXUINT UINT_MAX
|
||||
#define G_MINLONG LONG_MIN
|
||||
#define G_MAXLONG LONG_MAX
|
||||
#define G_MAXULONG ULONG_MAX
|
||||
|
||||
typedef signed char gint8;
|
||||
typedef unsigned char guint8;
|
||||
|
||||
typedef signed short gint16;
|
||||
typedef unsigned short guint16;
|
||||
|
||||
#define G_GINT16_MODIFIER "h"
|
||||
#define G_GINT16_FORMAT "hi"
|
||||
#define G_GUINT16_FORMAT "hu"
|
||||
|
||||
|
||||
typedef signed int gint32;
|
||||
typedef unsigned int guint32;
|
||||
|
||||
#define G_GINT32_MODIFIER ""
|
||||
#define G_GINT32_FORMAT "i"
|
||||
#define G_GUINT32_FORMAT "u"
|
||||
|
||||
|
||||
#define G_HAVE_GINT64 1 /* deprecated, always true */
|
||||
|
||||
typedef signed long gint64;
|
||||
typedef unsigned long guint64;
|
||||
|
||||
#define G_GINT64_CONSTANT(val) (val##L)
|
||||
#define G_GUINT64_CONSTANT(val) (val##UL)
|
||||
|
||||
#define G_GINT64_MODIFIER "l"
|
||||
#define G_GINT64_FORMAT "li"
|
||||
#define G_GUINT64_FORMAT "lu"
|
||||
|
||||
|
||||
#define GLIB_SIZEOF_VOID_P 8
|
||||
#define GLIB_SIZEOF_LONG 8
|
||||
#define GLIB_SIZEOF_SIZE_T 8
|
||||
#define GLIB_SIZEOF_SSIZE_T 8
|
||||
|
||||
typedef signed long gssize;
|
||||
typedef unsigned long gsize;
|
||||
#define G_GSIZE_MODIFIER "l"
|
||||
#define G_GSSIZE_MODIFIER "l"
|
||||
#define G_GSIZE_FORMAT "lu"
|
||||
#define G_GSSIZE_FORMAT "li"
|
||||
|
||||
#define G_MAXSIZE G_MAXULONG
|
||||
#define G_MINSSIZE G_MINLONG
|
||||
#define G_MAXSSIZE G_MAXLONG
|
||||
|
||||
typedef gint64 goffset;
|
||||
#define G_MINOFFSET G_MININT64
|
||||
#define G_MAXOFFSET G_MAXINT64
|
||||
|
||||
#define G_GOFFSET_MODIFIER G_GINT64_MODIFIER
|
||||
#define G_GOFFSET_FORMAT G_GINT64_FORMAT
|
||||
#define G_GOFFSET_CONSTANT(val) G_GINT64_CONSTANT(val)
|
||||
|
||||
#define G_POLLFD_FORMAT "%d"
|
||||
|
||||
#define GPOINTER_TO_INT(p) ((gint) (glong) (p))
|
||||
#define GPOINTER_TO_UINT(p) ((guint) (gulong) (p))
|
||||
|
||||
#define GINT_TO_POINTER(i) ((gpointer) (glong) (i))
|
||||
#define GUINT_TO_POINTER(u) ((gpointer) (gulong) (u))
|
||||
|
||||
typedef signed long gintptr;
|
||||
typedef unsigned long guintptr;
|
||||
|
||||
#define G_GINTPTR_MODIFIER "l"
|
||||
#define G_GINTPTR_FORMAT "li"
|
||||
#define G_GUINTPTR_FORMAT "lu"
|
||||
|
||||
#define GLIB_MAJOR_VERSION 2
|
||||
#define GLIB_MINOR_VERSION 81
|
||||
#define GLIB_MICRO_VERSION 1
|
||||
|
||||
#define G_OS_UNIX
|
||||
|
||||
#define G_VA_COPY va_copy
|
||||
|
||||
#define G_VA_COPY_AS_ARRAY 1
|
||||
|
||||
#define G_HAVE_ISO_VARARGS 1
|
||||
|
||||
/* gcc-2.95.x supports both gnu style and ISO varargs, but if -ansi
|
||||
* is passed ISO vararg support is turned off, and there is no work
|
||||
* around to turn it on, so we unconditionally turn it off.
|
||||
*/
|
||||
#if __GNUC__ == 2 && __GNUC_MINOR__ == 95
|
||||
# undef G_HAVE_ISO_VARARGS
|
||||
#endif
|
||||
|
||||
#define G_HAVE_GROWING_STACK 0
|
||||
|
||||
#ifndef _MSC_VER
|
||||
# define G_HAVE_GNUC_VARARGS 1
|
||||
#endif
|
||||
|
||||
#if defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)
|
||||
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
|
||||
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
|
||||
#define G_GNUC_INTERNAL __hidden
|
||||
#elif defined (__GNUC__) && defined (G_HAVE_GNUC_VISIBILITY)
|
||||
#define G_GNUC_INTERNAL __attribute__((visibility("hidden")))
|
||||
#else
|
||||
#define G_GNUC_INTERNAL
|
||||
#endif
|
||||
|
||||
#define G_THREADS_ENABLED
|
||||
#define G_THREADS_IMPL_POSIX
|
||||
|
||||
#define G_ATOMIC_LOCK_FREE
|
||||
|
||||
#define GINT16_TO_LE(val) ((gint16) (val))
|
||||
#define GUINT16_TO_LE(val) ((guint16) (val))
|
||||
#define GINT16_TO_BE(val) ((gint16) GUINT16_SWAP_LE_BE (val))
|
||||
#define GUINT16_TO_BE(val) (GUINT16_SWAP_LE_BE (val))
|
||||
|
||||
#define GINT32_TO_LE(val) ((gint32) (val))
|
||||
#define GUINT32_TO_LE(val) ((guint32) (val))
|
||||
#define GINT32_TO_BE(val) ((gint32) GUINT32_SWAP_LE_BE (val))
|
||||
#define GUINT32_TO_BE(val) (GUINT32_SWAP_LE_BE (val))
|
||||
|
||||
#define GINT64_TO_LE(val) ((gint64) (val))
|
||||
#define GUINT64_TO_LE(val) ((guint64) (val))
|
||||
#define GINT64_TO_BE(val) ((gint64) GUINT64_SWAP_LE_BE (val))
|
||||
#define GUINT64_TO_BE(val) (GUINT64_SWAP_LE_BE (val))
|
||||
|
||||
#define GLONG_TO_LE(val) ((glong) GINT64_TO_LE (val))
|
||||
#define GULONG_TO_LE(val) ((gulong) GUINT64_TO_LE (val))
|
||||
#define GLONG_TO_BE(val) ((glong) GINT64_TO_BE (val))
|
||||
#define GULONG_TO_BE(val) ((gulong) GUINT64_TO_BE (val))
|
||||
#define GINT_TO_LE(val) ((gint) GINT32_TO_LE (val))
|
||||
#define GUINT_TO_LE(val) ((guint) GUINT32_TO_LE (val))
|
||||
#define GINT_TO_BE(val) ((gint) GINT32_TO_BE (val))
|
||||
#define GUINT_TO_BE(val) ((guint) GUINT32_TO_BE (val))
|
||||
#define GSIZE_TO_LE(val) ((gsize) GUINT64_TO_LE (val))
|
||||
#define GSSIZE_TO_LE(val) ((gssize) GINT64_TO_LE (val))
|
||||
#define GSIZE_TO_BE(val) ((gsize) GUINT64_TO_BE (val))
|
||||
#define GSSIZE_TO_BE(val) ((gssize) GINT64_TO_BE (val))
|
||||
#define G_BYTE_ORDER G_LITTLE_ENDIAN
|
||||
|
||||
#define GLIB_SYSDEF_POLLIN =1
|
||||
#define GLIB_SYSDEF_POLLOUT =4
|
||||
#define GLIB_SYSDEF_POLLPRI =2
|
||||
#define GLIB_SYSDEF_POLLHUP =16
|
||||
#define GLIB_SYSDEF_POLLERR =8
|
||||
#define GLIB_SYSDEF_POLLNVAL =32
|
||||
|
||||
/* No way to disable deprecation warnings for macros, so only emit deprecation
|
||||
* warnings on platforms where usage of this macro is broken */
|
||||
#if defined(__APPLE__) || defined(_MSC_VER) || defined(__CYGWIN__)
|
||||
#define G_MODULE_SUFFIX "so" GLIB_DEPRECATED_MACRO_IN_2_76
|
||||
#else
|
||||
#define G_MODULE_SUFFIX "so"
|
||||
#endif
|
||||
|
||||
typedef int GPid;
|
||||
#define G_PID_FORMAT "i"
|
||||
|
||||
#define GLIB_SYSDEF_AF_UNIX 1
|
||||
#define GLIB_SYSDEF_AF_INET 2
|
||||
#define GLIB_SYSDEF_AF_INET6 10
|
||||
|
||||
#define GLIB_SYSDEF_MSG_OOB 1
|
||||
#define GLIB_SYSDEF_MSG_PEEK 2
|
||||
#define GLIB_SYSDEF_MSG_DONTROUTE 4
|
||||
|
||||
#define G_DIR_SEPARATOR '/'
|
||||
#define G_DIR_SEPARATOR_S "/"
|
||||
#define G_SEARCHPATH_SEPARATOR ':'
|
||||
#define G_SEARCHPATH_SEPARATOR_S ":"
|
||||
|
||||
#undef G_HAVE_FREE_SIZED
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GLIBCONFIG_H__ */
|
||||
1
node_modules/@img/sharp-libvips-linux-x64/lib/index.js
generated
vendored
Normal file
1
node_modules/@img/sharp-libvips-linux-x64/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = __dirname;
|
||||
BIN
node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.42
generated
vendored
Normal file
BIN
node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.42
generated
vendored
Normal file
Binary file not shown.
42
node_modules/@img/sharp-libvips-linux-x64/package.json
generated
vendored
Normal file
42
node_modules/@img/sharp-libvips-linux-x64/package.json
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@img/sharp-libvips-linux-x64",
|
||||
"version": "1.0.4",
|
||||
"description": "Prebuilt libvips and dependencies for use with sharp on Linux (glibc) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/sharp-libvips.git",
|
||||
"directory": "npm/linux-x64"
|
||||
},
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"versions.json"
|
||||
],
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./lib": "./lib/index.js",
|
||||
"./package": "./package.json",
|
||||
"./versions": "./versions.json"
|
||||
},
|
||||
"config": {
|
||||
"glibc": ">=2.26"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
30
node_modules/@img/sharp-libvips-linux-x64/versions.json
generated
vendored
Normal file
30
node_modules/@img/sharp-libvips-linux-x64/versions.json
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"aom": "3.9.1",
|
||||
"archive": "3.7.4",
|
||||
"cairo": "1.18.0",
|
||||
"cgif": "0.4.1",
|
||||
"exif": "0.6.24",
|
||||
"expat": "2.6.2",
|
||||
"ffi": "3.4.6",
|
||||
"fontconfig": "2.15.0",
|
||||
"freetype": "2.13.2",
|
||||
"fribidi": "1.0.15",
|
||||
"glib": "2.81.1",
|
||||
"harfbuzz": "9.0.0",
|
||||
"heif": "1.18.2",
|
||||
"highway": "1.2.0",
|
||||
"imagequant": "2.4.1",
|
||||
"lcms": "2.16",
|
||||
"mozjpeg": "4.1.5",
|
||||
"pango": "1.54.0",
|
||||
"pixman": "0.43.4",
|
||||
"png": "1.6.43",
|
||||
"proxy-libintl": "0.4",
|
||||
"rsvg": "2.58.93",
|
||||
"spng": "0.7.4",
|
||||
"tiff": "4.6.0",
|
||||
"vips": "8.15.3",
|
||||
"webp": "1.4.0",
|
||||
"xml": "2.13.3",
|
||||
"zlib-ng": "2.2.1"
|
||||
}
|
||||
191
node_modules/@img/sharp-linux-x64/LICENSE
generated
vendored
Normal file
191
node_modules/@img/sharp-linux-x64/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
18
node_modules/@img/sharp-linux-x64/README.md
generated
vendored
Normal file
18
node_modules/@img/sharp-linux-x64/README.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# `@img/sharp-linux-x64`
|
||||
|
||||
Prebuilt sharp for use with Linux (glibc) x64.
|
||||
|
||||
## Licensing
|
||||
|
||||
Copyright 2013 Lovell Fuller and others.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
BIN
node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node
generated
vendored
Executable file
BIN
node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node
generated
vendored
Executable file
Binary file not shown.
46
node_modules/@img/sharp-linux-x64/package.json
generated
vendored
Normal file
46
node_modules/@img/sharp-linux-x64/package.json
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-x64",
|
||||
"version": "0.33.5",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/sharp.git",
|
||||
"directory": "npm/linux-x64"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./sharp.node": "./lib/sharp-linux-x64.node",
|
||||
"./package": "./package.json"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"config": {
|
||||
"glibc": ">=2.26"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
1
node_modules/@weborigami/async-tree/ReadMe.md
generated
vendored
Normal file
1
node_modules/@weborigami/async-tree/ReadMe.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This library contains definitions for asynchronous trees backed by standard JavaScript classes like `Object` and `Map` and standard browser APIs such as the [Origin Private File System](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system). The library also includes collections of helpers for common tree operations.
|
||||
4
node_modules/@weborigami/async-tree/browser.js
generated
vendored
Normal file
4
node_modules/@weborigami/async-tree/browser.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Exports for browser
|
||||
|
||||
export * from "./shared.js";
|
||||
export { default as BrowserFileTree } from "./src/drivers/BrowserFileTree.js";
|
||||
72
node_modules/@weborigami/async-tree/index.ts
generated
vendored
Normal file
72
node_modules/@weborigami/async-tree/index.ts
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { AsyncTree } from "@weborigami/types";
|
||||
|
||||
export * from "./main.js";
|
||||
|
||||
export type KeyFn = (key: any, innerTree: AsyncTree) => any;
|
||||
|
||||
/**
|
||||
* An object with a non-trivial `toString` method.
|
||||
*
|
||||
* TODO: We want to deliberately exclude the base `Object` class because its
|
||||
* `toString` method return non-useful strings like `[object Object]`. How can
|
||||
* we declare that in TypeScript?
|
||||
*/
|
||||
export type HasString = {
|
||||
toString(): string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A packed value is one that can be written to a file via fs.writeFile or into
|
||||
* an HTTP response via response.write, or readily converted to such a form.
|
||||
*/
|
||||
export type Packed = (ArrayBuffer | Buffer | ReadableStream | string | String | TypedArray) & {
|
||||
unpack?(): Promise<any>;
|
||||
};
|
||||
|
||||
export type PlainObject = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type ReduceFn = (values: any[], keys: any[], tree: AsyncTree) => Promise<any>;
|
||||
|
||||
export type StringLike = string | HasString;
|
||||
|
||||
export type NativeTreelike =
|
||||
any[] |
|
||||
AsyncTree |
|
||||
Function |
|
||||
Map<any, any> |
|
||||
PlainObject |
|
||||
Set<any>;
|
||||
|
||||
export type Treelike =
|
||||
NativeTreelike |
|
||||
Unpackable<NativeTreelike>;
|
||||
|
||||
export type TreeMapOptions = {
|
||||
deep?: boolean;
|
||||
description?: string;
|
||||
needsSourceValue?: boolean;
|
||||
inverseKey?: KeyFn;
|
||||
key?: KeyFn;
|
||||
value?: ValueKeyFn;
|
||||
};
|
||||
|
||||
export type TreeTransform = (treelike: Treelike) => AsyncTree;
|
||||
|
||||
export type TypedArray =
|
||||
Float32Array |
|
||||
Float64Array |
|
||||
Int8Array |
|
||||
Int16Array |
|
||||
Int32Array |
|
||||
Uint8Array |
|
||||
Uint8ClampedArray |
|
||||
Uint16Array |
|
||||
Uint32Array;
|
||||
|
||||
export type Unpackable<T> = {
|
||||
unpack(): Promise<T>
|
||||
};
|
||||
|
||||
export type ValueKeyFn = (value: any, key: any, innerTree: AsyncTree) => any;
|
||||
5
node_modules/@weborigami/async-tree/main.js
generated
vendored
Normal file
5
node_modules/@weborigami/async-tree/main.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Exports for Node.js
|
||||
|
||||
export * from "./shared.js";
|
||||
export { default as FileTree } from "./src/drivers/FileTree.js";
|
||||
export * as extension from "./src/extension.js";
|
||||
20
node_modules/@weborigami/async-tree/package.json
generated
vendored
Normal file
20
node_modules/@weborigami/async-tree/package.json
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@weborigami/async-tree",
|
||||
"version": "0.2.1",
|
||||
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
||||
"type": "module",
|
||||
"main": "./main.js",
|
||||
"browser": "./browser.js",
|
||||
"types": "./index.ts",
|
||||
"devDependencies": {
|
||||
"@types/node": "22.7.4",
|
||||
"typescript": "5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@weborigami/types": "0.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test --test-reporter=spec",
|
||||
"typecheck": "tsc"
|
||||
}
|
||||
}
|
||||
32
node_modules/@weborigami/async-tree/shared.js
generated
vendored
Normal file
32
node_modules/@weborigami/async-tree/shared.js
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Exports for both Node.js and browser
|
||||
|
||||
export { default as calendarTree } from "./src/drivers/calendarTree.js";
|
||||
export { default as DeepMapTree } from "./src/drivers/DeepMapTree.js";
|
||||
export { default as DeferredTree } from "./src/drivers/DeferredTree.js";
|
||||
export { default as ExplorableSiteTree } from "./src/drivers/ExplorableSiteTree.js";
|
||||
export { default as FunctionTree } from "./src/drivers/FunctionTree.js";
|
||||
export { default as MapTree } from "./src/drivers/MapTree.js";
|
||||
export { default as SetTree } from "./src/drivers/SetTree.js";
|
||||
export { default as SiteTree } from "./src/drivers/SiteTree.js";
|
||||
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
||||
export * as jsonKeys from "./src/jsonKeys.js";
|
||||
export { default as cache } from "./src/operations/cache.js";
|
||||
export { default as cachedKeyFunctions } from "./src/operations/cachedKeyFunctions.js";
|
||||
export { default as concat } from "./src/operations/concat.js";
|
||||
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
||||
export { default as deepReverse } from "./src/operations/deepReverse.js";
|
||||
export { default as deepTake } from "./src/operations/deepTake.js";
|
||||
export { default as deepValues } from "./src/operations/deepValues.js";
|
||||
export { default as deepValuesIterator } from "./src/operations/deepValuesIterator.js";
|
||||
export { default as group } from "./src/operations/group.js";
|
||||
export { default as invokeFunctions } from "./src/operations/invokeFunctions.js";
|
||||
export { default as keyFunctionsForExtensions } from "./src/operations/keyFunctionsForExtensions.js";
|
||||
export { default as map } from "./src/operations/map.js";
|
||||
export { default as merge } from "./src/operations/merge.js";
|
||||
export { default as reverse } from "./src/operations/reverse.js";
|
||||
export { default as scope } from "./src/operations/scope.js";
|
||||
export { default as sort } from "./src/operations/sort.js";
|
||||
export { default as take } from "./src/operations/take.js";
|
||||
export * as symbols from "./src/symbols.js";
|
||||
export * as trailingSlash from "./src/trailingSlash.js";
|
||||
export * from "./src/utilities.js";
|
||||
24
node_modules/@weborigami/async-tree/src/Tree.d.ts
generated
vendored
Normal file
24
node_modules/@weborigami/async-tree/src/Tree.d.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { AsyncMutableTree, AsyncTree } from "@weborigami/types";
|
||||
import { PlainObject, ReduceFn, Treelike, TreeMapOptions, ValueKeyFn } from "../index.ts";
|
||||
|
||||
export function assign(target: Treelike, source: Treelike): Promise<AsyncTree>;
|
||||
export function clear(AsyncTree: AsyncMutableTree): Promise<void>;
|
||||
export function entries(AsyncTree: AsyncTree): Promise<IterableIterator<any>>;
|
||||
export function forEach(AsyncTree: AsyncTree, callbackfn: (value: any, key: any) => Promise<void>): Promise<void>;
|
||||
export function from(obj: any, options?: { deep?: boolean, parent?: AsyncTree|null }): AsyncTree;
|
||||
export function has(AsyncTree: AsyncTree, key: any): Promise<boolean>;
|
||||
export function isAsyncMutableTree(obj: any): obj is AsyncMutableTree;
|
||||
export function isAsyncTree(obj: any): obj is AsyncTree;
|
||||
export function isTraversable(obj: any): boolean;
|
||||
export function isTreelike(obj: any): obj is Treelike;
|
||||
export function map(tree: Treelike, options: TreeMapOptions|ValueKeyFn): AsyncTree;
|
||||
export function mapReduce(tree: Treelike, mapFn: ValueKeyFn | null, reduceFn: ReduceFn): Promise<any>;
|
||||
export function paths(tree: Treelike, base?: string): string[];
|
||||
export function plain(tree: Treelike): Promise<PlainObject>;
|
||||
export function root(tree: Treelike): AsyncTree;
|
||||
export function remove(AsyncTree: AsyncMutableTree, key: any): Promise<boolean>;
|
||||
export function toFunction(tree: Treelike): Function;
|
||||
export function traverse(tree: Treelike, ...keys: any[]): Promise<any>;
|
||||
export function traverseOrThrow(tree: Treelike, ...keys: any[]): Promise<any>;
|
||||
export function traversePath(tree: Treelike, path: string): Promise<any>;
|
||||
export function values(AsyncTree: AsyncTree): Promise<IterableIterator<any>>;
|
||||
497
node_modules/@weborigami/async-tree/src/Tree.js
generated
vendored
Normal file
497
node_modules/@weborigami/async-tree/src/Tree.js
generated
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
import DeferredTree from "./drivers/DeferredTree.js";
|
||||
import FunctionTree from "./drivers/FunctionTree.js";
|
||||
import MapTree from "./drivers/MapTree.js";
|
||||
import SetTree from "./drivers/SetTree.js";
|
||||
import { DeepObjectTree, ObjectTree } from "./internal.js";
|
||||
import * as symbols from "./symbols.js";
|
||||
import * as trailingSlash from "./trailingSlash.js";
|
||||
import * as utilities from "./utilities.js";
|
||||
import {
|
||||
castArrayLike,
|
||||
isPacked,
|
||||
isPlainObject,
|
||||
isUnpackable,
|
||||
toPlainValue,
|
||||
} from "./utilities.js";
|
||||
|
||||
/**
|
||||
* Helper functions for working with async trees
|
||||
*
|
||||
* @typedef {import("../index.ts").PlainObject} PlainObject
|
||||
* @typedef {import("../index.ts").ReduceFn} ReduceFn
|
||||
* @typedef {import("../index.ts").Treelike} Treelike
|
||||
* @typedef {import("../index.ts").ValueKeyFn} ValueKeyFn
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*/
|
||||
|
||||
const treeModule = this;
|
||||
|
||||
/**
|
||||
* Apply the key/values pairs from the source tree to the target tree.
|
||||
*
|
||||
* If a key exists in both trees, and the values in both trees are
|
||||
* subtrees, then the subtrees will be merged recursively. Otherwise, the
|
||||
* value from the source tree will overwrite the value in the target tree.
|
||||
*
|
||||
* @param {AsyncMutableTree} target
|
||||
* @param {AsyncTree} source
|
||||
*/
|
||||
export async function assign(target, source) {
|
||||
const targetTree = from(target);
|
||||
const sourceTree = from(source);
|
||||
if (!isAsyncMutableTree(targetTree)) {
|
||||
throw new TypeError("Target must be a mutable asynchronous tree");
|
||||
}
|
||||
// Fire off requests to update all keys, then wait for all of them to finish.
|
||||
const keys = Array.from(await sourceTree.keys());
|
||||
const promises = keys.map(async (key) => {
|
||||
const sourceValue = await sourceTree.get(key);
|
||||
if (isAsyncTree(sourceValue)) {
|
||||
const targetValue = await targetTree.get(key);
|
||||
if (isAsyncMutableTree(targetValue)) {
|
||||
// Both source and target are trees; recurse.
|
||||
await assign(targetValue, sourceValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Copy the value from the source to the target.
|
||||
await /** @type {any} */ (targetTree).set(key, sourceValue);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
return targetTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all entries from the tree.
|
||||
*
|
||||
* @param {AsyncMutableTree} tree
|
||||
*/
|
||||
export async function clear(tree) {
|
||||
const keys = Array.from(await tree.keys());
|
||||
const promises = keys.map((key) => tree.set(key, undefined));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new `Iterator` object that contains a two-member array of `[key,
|
||||
* value]` for each element in the specific node of the tree.
|
||||
*
|
||||
* @param {AsyncTree} tree
|
||||
*/
|
||||
export async function entries(tree) {
|
||||
const keys = Array.from(await tree.keys());
|
||||
const promises = keys.map(async (key) => [key, await tree.get(key)]);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls callbackFn once for each key-value pair present in the specific node of
|
||||
* the tree.
|
||||
*
|
||||
* @param {AsyncTree} tree
|
||||
* @param {Function} callbackFn
|
||||
*/
|
||||
export async function forEach(tree, callbackFn) {
|
||||
const keys = Array.from(await tree.keys());
|
||||
const promises = keys.map(async (key) => {
|
||||
const value = await tree.get(key);
|
||||
return callbackFn(value, key);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cast the indicated object to an async tree.
|
||||
*
|
||||
* If the object is a plain object, it will be converted to an ObjectTree. The
|
||||
* optional `deep` option can be set to `true` to convert a plain object to a
|
||||
* DeepObjectTree. The optional `parent` parameter will be used as the default
|
||||
* parent of the new tree.
|
||||
*
|
||||
* @param {Treelike | Object} object
|
||||
* @param {{ deep?: boolean, parent?: AsyncTree|null }} [options]
|
||||
* @returns {AsyncTree}
|
||||
*/
|
||||
export function from(object, options = {}) {
|
||||
const deep = options.deep ?? object[symbols.deep];
|
||||
let tree;
|
||||
if (isAsyncTree(object)) {
|
||||
// Argument already supports the tree interface.
|
||||
// @ts-ignore
|
||||
return object;
|
||||
} else if (typeof object === "function") {
|
||||
tree = new FunctionTree(object);
|
||||
} else if (object instanceof Map) {
|
||||
tree = new MapTree(object);
|
||||
} else if (object instanceof Set) {
|
||||
tree = new SetTree(object);
|
||||
} else if (isPlainObject(object) || object instanceof Array) {
|
||||
tree = deep ? new DeepObjectTree(object) : new ObjectTree(object);
|
||||
} else if (isUnpackable(object)) {
|
||||
async function AsyncFunction() {} // Sample async function
|
||||
tree =
|
||||
object.unpack instanceof AsyncFunction.constructor
|
||||
? // Async unpack: return a deferred tree.
|
||||
new DeferredTree(object.unpack, { deep })
|
||||
: // Synchronous unpack: cast the result of unpack() to a tree.
|
||||
from(object.unpack());
|
||||
} else if (object && typeof object === "object") {
|
||||
// An instance of some class.
|
||||
tree = new ObjectTree(object);
|
||||
} else if (
|
||||
typeof object === "string" ||
|
||||
typeof object === "number" ||
|
||||
typeof object === "boolean"
|
||||
) {
|
||||
// A primitive value; box it into an object and construct a tree.
|
||||
const boxed = utilities.box(object);
|
||||
tree = new ObjectTree(boxed);
|
||||
} else {
|
||||
throw new TypeError("Couldn't convert argument to an async tree");
|
||||
}
|
||||
|
||||
if (!tree.parent && options.parent) {
|
||||
tree.parent = options.parent;
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the specific node of the tree has a
|
||||
* value for the given `key`.
|
||||
*
|
||||
* @param {AsyncTree} tree
|
||||
* @param {any} key
|
||||
*/
|
||||
export async function has(tree, key) {
|
||||
const value = await tree.get(key);
|
||||
return value !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the indicated object is an async tree.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is AsyncTree}
|
||||
*/
|
||||
export function isAsyncTree(obj) {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === "object" &&
|
||||
typeof obj.get === "function" &&
|
||||
typeof obj.keys === "function" &&
|
||||
// JavaScript Map look like trees but can't be extended the same way, so we
|
||||
// report false.
|
||||
!(obj instanceof Map)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the indicated object is an async mutable tree.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is AsyncMutableTree}
|
||||
*/
|
||||
export function isAsyncMutableTree(obj) {
|
||||
return (
|
||||
isAsyncTree(obj) && typeof (/** @type {any} */ (obj).set) === "function"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the object can be traversed via the `traverse()` method. The
|
||||
* object must be either treelike or a packed object with an `unpack()` method.
|
||||
*
|
||||
* @param {any} object
|
||||
*/
|
||||
export function isTraversable(object) {
|
||||
return (
|
||||
isTreelike(object) ||
|
||||
(isPacked(object) && /** @type {any} */ (object).unpack instanceof Function)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the indicated object can be directly treated as an
|
||||
* asynchronous tree. This includes:
|
||||
*
|
||||
* - An object that implements the AsyncTree interface (including
|
||||
* AsyncTree instances)
|
||||
* - A function
|
||||
* - An `Array` instance
|
||||
* - A `Map` instance
|
||||
* - A `Set` instance
|
||||
* - A plain object
|
||||
*
|
||||
* Note: the `from()` method accepts any JavaScript object, but `isTreelike`
|
||||
* returns `false` for an object that isn't one of the above types.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is Treelike}
|
||||
*/
|
||||
export function isTreelike(obj) {
|
||||
return (
|
||||
isAsyncTree(obj) ||
|
||||
obj instanceof Array ||
|
||||
obj instanceof Function ||
|
||||
obj instanceof Map ||
|
||||
obj instanceof Set ||
|
||||
isPlainObject(obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new tree with deeply-mapped values of the original tree.
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @param {ValueKeyFn} valueFn
|
||||
*/
|
||||
export { default as map } from "./operations/map.js";
|
||||
|
||||
/**
|
||||
* Map and reduce a tree.
|
||||
*
|
||||
* This is done in as parallel fashion as possible. Each of the tree's values
|
||||
* will be requested in an async call, then those results will be awaited
|
||||
* collectively. If a mapFn is provided, it will be invoked to convert each
|
||||
* value to a mapped value; otherwise, values will be used as is. When the
|
||||
* values have been obtained, all the values and keys will be passed to the
|
||||
* reduceFn, which should consolidate those into a single result.
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @param {ValueKeyFn|null} valueFn
|
||||
* @param {ReduceFn} reduceFn
|
||||
*/
|
||||
export async function mapReduce(treelike, valueFn, reduceFn) {
|
||||
const tree = from(treelike);
|
||||
|
||||
// We're going to fire off all the get requests in parallel, as quickly as
|
||||
// the keys come in. We call the tree's `get` method for each key, but
|
||||
// *don't* wait for it yet.
|
||||
const keys = Array.from(await tree.keys());
|
||||
const promises = keys.map((key) =>
|
||||
tree.get(key).then((value) =>
|
||||
// If the value is a subtree, recurse.
|
||||
isAsyncTree(value)
|
||||
? mapReduce(value, valueFn, reduceFn)
|
||||
: valueFn
|
||||
? valueFn(value, key, tree)
|
||||
: value
|
||||
)
|
||||
);
|
||||
|
||||
// Wait for all the promises to resolve. Because the promises were captured
|
||||
// in the same order as the keys, the values will also be in the same order.
|
||||
const values = await Promise.all(promises);
|
||||
|
||||
// Reduce the values to a single result.
|
||||
return reduceFn(values, keys, tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns slash-separated paths for all values in the tree.
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @param {string?} base
|
||||
*/
|
||||
export async function paths(treelike, base = "") {
|
||||
const tree = from(treelike);
|
||||
const result = [];
|
||||
for (const key of await tree.keys()) {
|
||||
const separator = trailingSlash.has(base) ? "" : "/";
|
||||
const valuePath = base ? `${base}${separator}${key}` : key;
|
||||
const value = await tree.get(key);
|
||||
if (await isAsyncTree(value)) {
|
||||
const subPaths = await paths(value, valuePath);
|
||||
result.push(...subPaths);
|
||||
} else {
|
||||
result.push(valuePath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an asynchronous tree into a synchronous plain JavaScript object.
|
||||
*
|
||||
* The result's keys will be the tree's keys cast to strings. Any tree value
|
||||
* that is itself a tree will be similarly converted to a plain object.
|
||||
*
|
||||
* Any trailing slashes in keys will be removed.
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @returns {Promise<PlainObject|Array>}
|
||||
*/
|
||||
export async function plain(treelike) {
|
||||
return mapReduce(treelike, toPlainValue, (values, keys, tree) => {
|
||||
// Special case for an empty tree: if based on array, return array.
|
||||
if (tree instanceof ObjectTree && keys.length === 0) {
|
||||
return /** @type {any} */ (tree).object instanceof Array ? [] : {};
|
||||
}
|
||||
// Normalize slashes in keys.
|
||||
keys = keys.map(trailingSlash.remove);
|
||||
return castArrayLike(keys, values);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value for the given key from the specific node of the tree.
|
||||
*
|
||||
* Note: The corresponding `Map` method is `delete`, not `remove`. However,
|
||||
* `delete` is a reserved word in JavaScript, so this uses `remove` instead.
|
||||
*
|
||||
* @param {AsyncMutableTree} tree
|
||||
* @param {any} key
|
||||
*/
|
||||
export async function remove(tree, key) {
|
||||
const exists = await has(tree, key);
|
||||
if (exists) {
|
||||
await tree.set(key, undefined);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the `parent` chain to find the root of the tree.
|
||||
*
|
||||
* @param {AsyncTree} tree
|
||||
*/
|
||||
export function root(tree) {
|
||||
let current = from(tree);
|
||||
while (current.parent) {
|
||||
current = current.parent;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that invokes the tree's `get` method.
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toFunction(treelike) {
|
||||
const tree = from(treelike);
|
||||
return tree.get.bind(tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value at the corresponding path of keys.
|
||||
*
|
||||
* @this {any}
|
||||
* @param {Treelike} treelike
|
||||
* @param {...any} keys
|
||||
*/
|
||||
export async function traverse(treelike, ...keys) {
|
||||
try {
|
||||
// Await the result here so that, if the path doesn't exist, the catch
|
||||
// block below will catch the exception.
|
||||
return await traverseOrThrow.call(this, treelike, ...keys);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error instanceof TraverseError) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value at the corresponding path of keys. Throw if any interior
|
||||
* step of the path doesn't lead to a result.
|
||||
*
|
||||
* @this {AsyncTree|null|undefined}
|
||||
* @param {Treelike} treelike
|
||||
* @param {...any} keys
|
||||
*/
|
||||
export async function traverseOrThrow(treelike, ...keys) {
|
||||
// Start our traversal at the root of the tree.
|
||||
/** @type {any} */
|
||||
let value = treelike;
|
||||
let position = 0;
|
||||
|
||||
// If traversal operation was called with a `this` context, use that as the
|
||||
// target for function calls.
|
||||
const target = this === treeModule ? undefined : this;
|
||||
|
||||
// Process all the keys.
|
||||
const remainingKeys = keys.slice();
|
||||
let key;
|
||||
while (remainingKeys.length > 0) {
|
||||
if (value === undefined) {
|
||||
throw new TraverseError("A null or undefined value can't be traversed", {
|
||||
tree: treelike,
|
||||
keys,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
// If the value is packed and can be unpacked, unpack it.
|
||||
if (isUnpackable(value)) {
|
||||
value = await value.unpack();
|
||||
}
|
||||
|
||||
if (value instanceof Function) {
|
||||
// Value is a function: call it with the remaining keys.
|
||||
const fn = value;
|
||||
// We'll take as many keys as the function's length, but at least one.
|
||||
let fnKeyCount = Math.max(fn.length, 1);
|
||||
const args = remainingKeys.splice(0, fnKeyCount);
|
||||
key = null;
|
||||
value = await fn.call(target, ...args);
|
||||
} else if (isTraversable(value) || typeof value === "object") {
|
||||
// Value is some other treelike object: cast it to a tree.
|
||||
const tree = from(value);
|
||||
// Get the next key.
|
||||
key = remainingKeys.shift();
|
||||
// Get the value for the key.
|
||||
value = await tree.get(key);
|
||||
} else {
|
||||
// Value can't be traversed
|
||||
throw new TraverseError("Tried to traverse a value that's not treelike", {
|
||||
tree: treelike,
|
||||
keys,
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a slash-separated path like "foo/bar", traverse the keys "foo/" and
|
||||
* "bar" and return the resulting value.
|
||||
*
|
||||
* @param {Treelike} tree
|
||||
* @param {string} path
|
||||
*/
|
||||
export async function traversePath(tree, path) {
|
||||
const keys = utilities.keysFromPath(path);
|
||||
return traverse(tree, ...keys);
|
||||
}
|
||||
|
||||
// Error class thrown by traverseOrThrow()
|
||||
class TraverseError extends ReferenceError {
|
||||
constructor(message, options) {
|
||||
super(message);
|
||||
this.name = "TraverseError";
|
||||
Object.assign(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the values in the specific node of the tree.
|
||||
*
|
||||
* @param {AsyncTree} tree
|
||||
*/
|
||||
export async function values(tree) {
|
||||
const keys = Array.from(await tree.keys());
|
||||
const promises = keys.map(async (key) => tree.get(key));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
176
node_modules/@weborigami/async-tree/src/drivers/BrowserFileTree.js
generated
vendored
Normal file
176
node_modules/@weborigami/async-tree/src/drivers/BrowserFileTree.js
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
import {
|
||||
hiddenFileNames,
|
||||
isStringLike,
|
||||
naturalOrder,
|
||||
setParent,
|
||||
} from "../utilities.js";
|
||||
|
||||
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
||||
|
||||
/**
|
||||
* A tree of files backed by a browser-hosted file system such as the standard
|
||||
* Origin Private File System or the (as of October 2023) experimental File
|
||||
* System Access API.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @implements {AsyncMutableTree}
|
||||
*/
|
||||
export default class BrowserFileTree {
|
||||
/**
|
||||
* Construct a tree of files backed by a browser-hosted file system.
|
||||
*
|
||||
* The directory handle can be obtained via any of the [methods that return a
|
||||
* FileSystemDirectoryHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle).
|
||||
* If no directory is supplied, the tree is rooted at the Origin Private File
|
||||
* System for the current site.
|
||||
*
|
||||
* @param {FileSystemDirectoryHandle} [directoryHandle]
|
||||
*/
|
||||
constructor(directoryHandle) {
|
||||
/** @type {FileSystemDirectoryHandle}
|
||||
* @ts-ignore */
|
||||
this.directory = directoryHandle;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key.
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
if (key === "") {
|
||||
// Can't have a file with no name
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Remove trailing slash if present
|
||||
key = trailingSlash.remove(key);
|
||||
|
||||
const directory = await this.getDirectory();
|
||||
|
||||
// Try the key as a subfolder name
|
||||
try {
|
||||
const subfolderHandle = await directory.getDirectoryHandle(key);
|
||||
const value = Reflect.construct(this.constructor, [subfolderHandle]);
|
||||
setParent(value, this);
|
||||
return value;
|
||||
} catch (error) {
|
||||
if (
|
||||
!(
|
||||
error instanceof DOMException &&
|
||||
(error.name === "NotFoundError" || error.name === "TypeMismatchError")
|
||||
)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Try the key as a file name
|
||||
try {
|
||||
const fileHandle = await directory.getFileHandle(key);
|
||||
const file = await fileHandle.getFile();
|
||||
const buffer = file.arrayBuffer();
|
||||
setParent(buffer, this);
|
||||
return buffer;
|
||||
} catch (error) {
|
||||
if (!(error instanceof DOMException && error.name === "NotFoundError")) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Return the directory handle, creating it if necessary. We can't create the
|
||||
// default value in the constructor because we need to await it.
|
||||
async getDirectory() {
|
||||
this.directory ??= await navigator.storage.getDirectory();
|
||||
return this.directory;
|
||||
}
|
||||
|
||||
async keys() {
|
||||
const directory = await this.getDirectory();
|
||||
let keys = [];
|
||||
// @ts-ignore
|
||||
for await (const entryKey of directory.keys()) {
|
||||
// Check if the entry is a subfolder
|
||||
const baseKey = trailingSlash.remove(entryKey);
|
||||
const subfolderHandle = await directory
|
||||
.getDirectoryHandle(baseKey)
|
||||
.catch(() => null);
|
||||
const isSubfolder = subfolderHandle !== null;
|
||||
|
||||
const key = trailingSlash.toggle(entryKey, isSubfolder);
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
// Filter out unhelpful file names.
|
||||
keys = keys.filter((key) => !hiddenFileNames.includes(key));
|
||||
keys.sort(naturalOrder);
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
const baseKey = trailingSlash.remove(key);
|
||||
const directory = await this.getDirectory();
|
||||
|
||||
if (value === undefined) {
|
||||
// Delete file.
|
||||
try {
|
||||
await directory.removeEntry(baseKey);
|
||||
} catch (error) {
|
||||
// If the file didn't exist, ignore the error.
|
||||
if (
|
||||
!(error instanceof DOMException && error.name === "NotFoundError")
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Treat null value as empty string; will create an empty file.
|
||||
if (value === null) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
// True if fs.writeFile can directly write the value to a file.
|
||||
let isWriteable =
|
||||
value instanceof ArrayBuffer ||
|
||||
value instanceof TypedArray ||
|
||||
value instanceof DataView ||
|
||||
value instanceof Blob;
|
||||
|
||||
if (!isWriteable && isStringLike(value)) {
|
||||
// Value has a meaningful `toString` method, use that.
|
||||
value = String(value);
|
||||
isWriteable = true;
|
||||
}
|
||||
|
||||
if (isWriteable) {
|
||||
// Write file.
|
||||
const fileHandle = await directory.getFileHandle(baseKey, {
|
||||
create: true,
|
||||
});
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(value);
|
||||
await writable.close();
|
||||
} else if (Tree.isTreelike(value)) {
|
||||
// Treat value as a tree and write it out as a subdirectory.
|
||||
const subdirectory = await directory.getDirectoryHandle(baseKey, {
|
||||
create: true,
|
||||
});
|
||||
const destTree = Reflect.construct(this.constructor, [subdirectory]);
|
||||
await Tree.assign(destTree, value);
|
||||
} else {
|
||||
const typeName = value?.constructor?.name ?? "unknown";
|
||||
throw new TypeError(`Cannot write a value of type ${typeName} as ${key}`);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
23
node_modules/@weborigami/async-tree/src/drivers/DeepMapTree.js
generated
vendored
Normal file
23
node_modules/@weborigami/async-tree/src/drivers/DeepMapTree.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import MapTree from "./MapTree.js";
|
||||
|
||||
export default class DeepMapTree extends MapTree {
|
||||
async get(key) {
|
||||
let value = await super.get(key);
|
||||
|
||||
if (value instanceof Map) {
|
||||
value = Reflect.construct(this.constructor, [value]);
|
||||
}
|
||||
|
||||
if (Tree.isAsyncTree(value) && !value.parent) {
|
||||
value.parent = this;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isSubtree(value) {
|
||||
return value instanceof Map || Tree.isAsyncTree(value);
|
||||
}
|
||||
}
|
||||
19
node_modules/@weborigami/async-tree/src/drivers/DeepObjectTree.js
generated
vendored
Normal file
19
node_modules/@weborigami/async-tree/src/drivers/DeepObjectTree.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ObjectTree, Tree } from "../internal.js";
|
||||
import { isPlainObject } from "../utilities.js";
|
||||
|
||||
export default class DeepObjectTree extends ObjectTree {
|
||||
async get(key) {
|
||||
let value = await super.get(key);
|
||||
if (value instanceof Array || isPlainObject(value)) {
|
||||
value = Reflect.construct(this.constructor, [value]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isSubtree(value) {
|
||||
return (
|
||||
value instanceof Array || isPlainObject(value) || Tree.isAsyncTree(value)
|
||||
);
|
||||
}
|
||||
}
|
||||
81
node_modules/@weborigami/async-tree/src/drivers/DeferredTree.js
generated
vendored
Normal file
81
node_modules/@weborigami/async-tree/src/drivers/DeferredTree.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* A tree that is loaded lazily.
|
||||
*
|
||||
* This is useful in situations that must return a tree synchronously. If
|
||||
* constructing the tree requires an asynchronous operation, this class can be
|
||||
* used as a wrapper that can be returned immediately. The tree will be loaded
|
||||
* the first time the keys() or get() functions are called.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @implements {AsyncTree}
|
||||
*/
|
||||
export default class DeferredTree {
|
||||
/**
|
||||
* @param {Function|Promise<any>} loader
|
||||
* @param {{ deep?: boolean }} [options]
|
||||
*/
|
||||
constructor(loader, options) {
|
||||
this.loader = loader;
|
||||
this.treePromise = null;
|
||||
this._tree = null;
|
||||
this._parentUntilLoaded = null;
|
||||
this._deep = options?.deep;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
const tree = await this.tree();
|
||||
return tree.get(key);
|
||||
}
|
||||
|
||||
async loadResult() {
|
||||
if (!(this.loader instanceof Promise)) {
|
||||
this.loader = this.loader();
|
||||
}
|
||||
return this.loader;
|
||||
}
|
||||
|
||||
async keys() {
|
||||
const tree = await this.tree();
|
||||
return tree.keys();
|
||||
}
|
||||
|
||||
// A deferred tree's parent generally comes from the loaded tree. However, if
|
||||
// someone tries to get or set the parent before the tree is loaded, we store
|
||||
// that parent reference and apply it once the tree is loaded.
|
||||
get parent() {
|
||||
return this._tree?.parent ?? this._parentUntilLoaded;
|
||||
}
|
||||
set parent(parent) {
|
||||
if (this._tree && !this._tree.parent) {
|
||||
this._tree.parent = parent;
|
||||
} else {
|
||||
this._parentUntilLoaded = parent;
|
||||
}
|
||||
}
|
||||
|
||||
async tree() {
|
||||
if (this._tree) {
|
||||
return this._tree;
|
||||
}
|
||||
|
||||
// Use a promise to ensure the treelike is only converted to a tree once.
|
||||
this.treePromise ??= this.loadResult().then((treelike) => {
|
||||
const options =
|
||||
this._deep !== undefined ? { deep: this._deep } : undefined;
|
||||
this._tree = Tree.from(treelike, options);
|
||||
if (this._parentUntilLoaded) {
|
||||
// Now that the tree has been loaded, we can set its parent if it hasn't
|
||||
// already been set.
|
||||
if (!this._tree.parent) {
|
||||
this._tree.parent = this._parentUntilLoaded;
|
||||
}
|
||||
this._parentUntilLoaded = null;
|
||||
}
|
||||
return this._tree;
|
||||
});
|
||||
|
||||
return this.treePromise;
|
||||
}
|
||||
}
|
||||
52
node_modules/@weborigami/async-tree/src/drivers/ExplorableSiteTree.js
generated
vendored
Normal file
52
node_modules/@weborigami/async-tree/src/drivers/ExplorableSiteTree.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import SiteTree from "./SiteTree.js";
|
||||
|
||||
/**
|
||||
* A [SiteTree](SiteTree.html) that implements the [JSON Keys](jsonKeys.html)
|
||||
* protocol. This enables a `keys()` method that can return the keys of a site
|
||||
* route even though such a mechanism is not built into the HTTP protocol.
|
||||
*/
|
||||
export default class ExplorableSiteTree extends SiteTree {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.serverKeysPromise = undefined;
|
||||
}
|
||||
|
||||
async getServerKeys() {
|
||||
// We use a promise to ensure we only check for keys once.
|
||||
const href = new URL(".keys.json", this.href).href;
|
||||
this.serverKeysPromise ??= fetch(href)
|
||||
.then((response) => (response.ok ? response.text() : null))
|
||||
.then((text) => {
|
||||
try {
|
||||
return text ? JSON.parse(text) : null;
|
||||
} catch (error) {
|
||||
// Got a response, but it's not JSON. Most likely the site doesn't
|
||||
// actually have a .keys.json file, and is returning a Not Found page,
|
||||
// but hasn't set the correct 404 status code.
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return this.serverKeysPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keys of the site route. For this to work, the route must have a
|
||||
* `.keys.json` file that contains a JSON array of string keys.
|
||||
*
|
||||
* @returns {Promise<Iterable<string>>}
|
||||
*/
|
||||
async keys() {
|
||||
const serverKeys = await this.getServerKeys();
|
||||
return serverKeys ?? [];
|
||||
}
|
||||
|
||||
processResponse(response) {
|
||||
// If the response was redirected to a route that ends with a slash, and the
|
||||
// site is an explorable site, we return a tree for the new route.
|
||||
if (response.ok && response.redirected && response.url.endsWith("/")) {
|
||||
return Reflect.construct(this.constructor, [response.url]);
|
||||
}
|
||||
|
||||
return super.processResponse(response);
|
||||
}
|
||||
}
|
||||
268
node_modules/@weborigami/async-tree/src/drivers/FileTree.js
generated
vendored
Normal file
268
node_modules/@weborigami/async-tree/src/drivers/FileTree.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
import {
|
||||
getRealmObjectPrototype,
|
||||
hiddenFileNames,
|
||||
isPacked,
|
||||
isPlainObject,
|
||||
naturalOrder,
|
||||
setParent,
|
||||
} from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A file system tree via the Node file system API.
|
||||
*
|
||||
* File values are returned as Uint8Array instances. The underlying Node fs API
|
||||
* returns file contents as instances of the node-specific Buffer class, but
|
||||
* that class has some incompatible method implementations; see
|
||||
* https://nodejs.org/api/buffer.html#buffers-and-typedarrays. For greater
|
||||
* compatibility, files are returned as standard Uint8Array instances instead.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @implements {AsyncMutableTree}
|
||||
*/
|
||||
export default class FileTree {
|
||||
/**
|
||||
* @param {string|URL} location
|
||||
*/
|
||||
constructor(location) {
|
||||
if (location instanceof URL) {
|
||||
location = location.href;
|
||||
} else if (
|
||||
!(
|
||||
typeof location === "string" ||
|
||||
/** @type {any} */ (location) instanceof String
|
||||
)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`FileTree constructor needs a string or URL, received an instance of ${
|
||||
/** @type {any} */ (location)?.constructor?.name
|
||||
}`
|
||||
);
|
||||
}
|
||||
this.dirname = location.startsWith("file://")
|
||||
? fileURLToPath(location)
|
||||
: path.resolve(process.cwd(), location);
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
if (key === "") {
|
||||
// Can't have a file with no name
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Remove trailing slash if present
|
||||
key = trailingSlash.remove(key);
|
||||
const filePath = path.resolve(this.dirname, key);
|
||||
|
||||
let stats;
|
||||
try {
|
||||
stats = await fs.stat(filePath);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code === "ENOENT" /* File not found */) {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
let value;
|
||||
if (stats.isDirectory()) {
|
||||
// Return subdirectory as a tree
|
||||
value = Reflect.construct(this.constructor, [filePath]);
|
||||
} else {
|
||||
// Return file contents as a standard Uint8Array
|
||||
const buffer = await fs.readFile(filePath);
|
||||
value = Uint8Array.from(buffer);
|
||||
}
|
||||
|
||||
setParent(value, this);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the names of the files/subdirectories in this directory.
|
||||
*/
|
||||
async keys() {
|
||||
let entries;
|
||||
try {
|
||||
entries = await fs.readdir(this.dirname, { withFileTypes: true });
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
entries = [];
|
||||
}
|
||||
|
||||
// Add slashes to directory names.
|
||||
let names = await Promise.all(
|
||||
entries.map(async (entry) =>
|
||||
trailingSlash.toggle(entry.name, await isDirectory(entry, this.dirname))
|
||||
)
|
||||
);
|
||||
|
||||
// Filter out unhelpful file names.
|
||||
names = names.filter((name) => !hiddenFileNames.includes(name));
|
||||
|
||||
// Node fs.readdir sort order appears to be unreliable; see, e.g.,
|
||||
// https://github.com/nodejs/node/issues/3232.
|
||||
names.sort(naturalOrder);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.dirname;
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
// Where are we going to write this value?
|
||||
const stringKey = key != null ? String(key) : "";
|
||||
const baseKey = trailingSlash.remove(stringKey);
|
||||
const destPath = path.resolve(this.dirname, baseKey);
|
||||
|
||||
if (value === undefined) {
|
||||
// Delete the file or directory.
|
||||
let stats;
|
||||
try {
|
||||
stats = await stat(destPath);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code === "ENOENT" /* File not found */) {
|
||||
return this;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (stats?.isDirectory()) {
|
||||
// Delete directory.
|
||||
await fs.rm(destPath, { recursive: true });
|
||||
} else if (stats) {
|
||||
// Delete file.
|
||||
await fs.unlink(destPath);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
// Invoke function; write out the result.
|
||||
value = await value();
|
||||
}
|
||||
|
||||
let packed = false;
|
||||
if (value === null) {
|
||||
// Treat null value as empty string; will create an empty file.
|
||||
value = "";
|
||||
packed = true;
|
||||
} else if (!(value instanceof String) && isPacked(value)) {
|
||||
// As of Node 22, fs.writeFile is incredibly slow for large String
|
||||
// instances. Instead of treating a String instance as a Packed value, we
|
||||
// want to consider it as a stringlike below. That will convert it to a
|
||||
// primitive string before writing — which is orders of magnitude faster.
|
||||
packed = true;
|
||||
} else if (typeof value.pack === "function") {
|
||||
// Pack the value for writing.
|
||||
value = await value.pack();
|
||||
packed = true;
|
||||
} else if (isStringLike(value)) {
|
||||
// Value has a meaningful `toString` method, use that.
|
||||
value = String(value);
|
||||
packed = true;
|
||||
}
|
||||
|
||||
if (packed) {
|
||||
// Single writeable value.
|
||||
if (value instanceof ArrayBuffer) {
|
||||
// Convert ArrayBuffer to Uint8Array, which Node.js can write directly.
|
||||
value = new Uint8Array(value);
|
||||
}
|
||||
// Ensure this directory exists.
|
||||
await fs.mkdir(this.dirname, { recursive: true });
|
||||
// Write out the value as the contents of a file.
|
||||
await fs.writeFile(destPath, value);
|
||||
} else if (isPlainObject(value) && Object.keys(value).length === 0) {
|
||||
// Special case: empty object means create an empty directory.
|
||||
await fs.mkdir(destPath, { recursive: true });
|
||||
} else if (Tree.isTreelike(value)) {
|
||||
// Treat value as a subtree and write it out as a subdirectory.
|
||||
const destTree = Reflect.construct(this.constructor, [destPath]);
|
||||
// Create the directory here, even if the subtree is empty.
|
||||
await fs.mkdir(destPath, { recursive: true });
|
||||
// Write out the subtree.
|
||||
await Tree.assign(destTree, value);
|
||||
} else {
|
||||
const typeName = value?.constructor?.name ?? "unknown";
|
||||
throw new TypeError(
|
||||
`Cannot write a value of type ${typeName} as ${stringKey}`
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return pathToFileURL(this.dirname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entry is a directory or is a symbolic link to a directory.
|
||||
*/
|
||||
async function isDirectory(entry, dirname) {
|
||||
if (entry.isSymbolicLink()) {
|
||||
const entryPath = path.resolve(dirname, entry.name);
|
||||
try {
|
||||
const realPath = await fs.realpath(entryPath);
|
||||
entry = await fs.stat(realPath);
|
||||
} catch (error) {
|
||||
// The slash isn't crucial, so if link doesn't work that's okay
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return entry.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the object is a string or object with a non-trival `toString`
|
||||
* method.
|
||||
*
|
||||
* @param {any} obj
|
||||
*/
|
||||
function isStringLike(obj) {
|
||||
if (typeof obj === "string") {
|
||||
return true;
|
||||
} else if (obj?.toString === undefined) {
|
||||
return false;
|
||||
} else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
|
||||
// The stupid Object.prototype.toString implementation always returns
|
||||
// "[object Object]", so if that's the only toString method the object has,
|
||||
// we return false.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the file information for the file/folder at the given path.
|
||||
// If it does not exist, return undefined.
|
||||
async function stat(filePath) {
|
||||
try {
|
||||
// Await the result here so that, if the file doesn't exist, the catch block
|
||||
// below will catch the exception.
|
||||
return await fs.stat(filePath);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code === "ENOENT" /* File not found */) {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
46
node_modules/@weborigami/async-tree/src/drivers/FunctionTree.js
generated
vendored
Normal file
46
node_modules/@weborigami/async-tree/src/drivers/FunctionTree.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import { setParent } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A tree defined by a function and an optional domain.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @implements {AsyncTree}
|
||||
*/
|
||||
export default class FunctionTree {
|
||||
/**
|
||||
* @param {function} fn the key->value function
|
||||
* @param {Iterable<any>} [domain] optional domain of the function
|
||||
*/
|
||||
constructor(fn, domain = []) {
|
||||
this.fn = fn;
|
||||
this.domain = domain;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application of the function to the given key.
|
||||
*
|
||||
* @param {any} key
|
||||
*/
|
||||
async get(key) {
|
||||
const value =
|
||||
this.fn.length <= 1
|
||||
? // Function takes no arguments, one argument, or a variable number of
|
||||
// arguments: invoke it.
|
||||
await this.fn.call(this.parent, key)
|
||||
: // Bind the key to the first parameter. Subsequent get calls will
|
||||
// eventually bind all parameters until only one remains. At that point,
|
||||
// the above condition will apply and the function will be invoked.
|
||||
Reflect.construct(this.constructor, [this.fn.bind(this.parent, key)]);
|
||||
setParent(value, this);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the function's domain (if defined) as the tree's keys. If no domain
|
||||
* was defined, this returns an empty iterator.
|
||||
*/
|
||||
async keys() {
|
||||
return this.domain;
|
||||
}
|
||||
}
|
||||
67
node_modules/@weborigami/async-tree/src/drivers/MapTree.js
generated
vendored
Normal file
67
node_modules/@weborigami/async-tree/src/drivers/MapTree.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
import { setParent } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A tree backed by a JavaScript `Map` object.
|
||||
*
|
||||
* Note: By design, the standard `Map` class already complies with the
|
||||
* `AsyncTree` interface. This class adds some additional tree behavior, such as
|
||||
* constructing subtree instances and setting their `parent` property. While
|
||||
* we'd like to construct this by subclassing `Map`, that class appears
|
||||
* puzzingly and deliberately implemented to break subclasses.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @implements {AsyncMutableTree}
|
||||
*/
|
||||
export default class MapTree {
|
||||
/**
|
||||
* @param {Iterable} [iterable]
|
||||
*/
|
||||
constructor(iterable = []) {
|
||||
this.map = new Map(iterable);
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
// Try key as is
|
||||
let value = this.map.get(key);
|
||||
if (value === undefined) {
|
||||
// Try the other variation of the key
|
||||
const alternateKey = trailingSlash.toggle(key);
|
||||
value = this.map.get(alternateKey);
|
||||
if (value === undefined) {
|
||||
// Key doesn't exist
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
value = await value;
|
||||
|
||||
if (value === undefined) {
|
||||
// Key exists but value is undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setParent(value, this);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isSubtree(value) {
|
||||
return Tree.isAsyncTree(value);
|
||||
}
|
||||
|
||||
async keys() {
|
||||
const keys = [];
|
||||
for (const [key, value] of this.map.entries()) {
|
||||
keys.push(trailingSlash.toggle(key, this.isSubtree(value)));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
this.map.set(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
142
node_modules/@weborigami/async-tree/src/drivers/ObjectTree.js
generated
vendored
Normal file
142
node_modules/@weborigami/async-tree/src/drivers/ObjectTree.js
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as symbols from "../symbols.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
import { getRealmObjectPrototype, setParent } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A tree defined by a plain object or array.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @implements {AsyncMutableTree}
|
||||
*/
|
||||
export default class ObjectTree {
|
||||
/**
|
||||
* Create a tree wrapping a given plain object or array.
|
||||
*
|
||||
* @param {any} object The object/array to wrap.
|
||||
*/
|
||||
constructor(object) {
|
||||
this.object = object;
|
||||
this.parent = object[symbols.parent] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the given key.
|
||||
*
|
||||
* @param {any} key
|
||||
*/
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key.
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
|
||||
// Does the object have the key with or without a trailing slash?
|
||||
const existingKey = findExistingKey(this.object, key);
|
||||
if (existingKey === null) {
|
||||
// Key doesn't exist
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let value = await this.object[existingKey];
|
||||
|
||||
if (value === undefined) {
|
||||
// Key exists but value is undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setParent(value, this);
|
||||
|
||||
if (typeof value === "function" && !Object.hasOwn(this.object, key)) {
|
||||
// Value is an inherited method; bind it to the object.
|
||||
value = value.bind(this.object);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
isSubtree(value) {
|
||||
return Tree.isAsyncTree(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the object's keys.
|
||||
*/
|
||||
async keys() {
|
||||
// Walk up the prototype chain to realm's Object.prototype.
|
||||
let obj = this.object;
|
||||
const objectPrototype = getRealmObjectPrototype(obj);
|
||||
|
||||
const result = new Set();
|
||||
while (obj && obj !== objectPrototype) {
|
||||
// Get the enumerable instance properties and the get/set properties.
|
||||
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
||||
const propertyNames = Object.entries(descriptors)
|
||||
.filter(
|
||||
([name, descriptor]) =>
|
||||
name !== "constructor" &&
|
||||
(descriptor.enumerable ||
|
||||
(descriptor.get !== undefined && descriptor.set !== undefined))
|
||||
)
|
||||
.map(([name, descriptor]) =>
|
||||
trailingSlash.has(name)
|
||||
? // Preserve existing slash
|
||||
name
|
||||
: // Add a slash if the value is a plain property and a subtree
|
||||
trailingSlash.toggle(
|
||||
name,
|
||||
descriptor.value !== undefined &&
|
||||
this.isSubtree(descriptor.value)
|
||||
)
|
||||
);
|
||||
for (const name of propertyNames) {
|
||||
result.add(name);
|
||||
}
|
||||
obj = Object.getPrototypeOf(obj);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for the given key. If the value is undefined, delete the key.
|
||||
*
|
||||
* @param {any} key
|
||||
* @param {any} value
|
||||
*/
|
||||
async set(key, value) {
|
||||
const existingKey = findExistingKey(this.object, key);
|
||||
|
||||
if (value === undefined) {
|
||||
// Delete the key if it exists.
|
||||
if (existingKey !== null) {
|
||||
delete this.object[existingKey];
|
||||
}
|
||||
} else {
|
||||
// If the key exists under a different form, delete the existing key.
|
||||
if (existingKey !== null && existingKey !== key) {
|
||||
delete this.object[existingKey];
|
||||
}
|
||||
|
||||
// Set the value for the key.
|
||||
this.object[key] = value;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function findExistingKey(object, key) {
|
||||
// First try key as is
|
||||
if (key in object) {
|
||||
return key;
|
||||
}
|
||||
// Try alternate form
|
||||
const alternateKey = trailingSlash.toggle(key);
|
||||
if (alternateKey in object) {
|
||||
return alternateKey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
34
node_modules/@weborigami/async-tree/src/drivers/SetTree.js
generated
vendored
Normal file
34
node_modules/@weborigami/async-tree/src/drivers/SetTree.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { setParent } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A tree of Set objects.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @implements {AsyncTree}
|
||||
*/
|
||||
export default class SetTree {
|
||||
/**
|
||||
* @param {Set} set
|
||||
*/
|
||||
constructor(set) {
|
||||
this.values = Array.from(set);
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key.
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
|
||||
const value = this.values[key];
|
||||
setParent(value, this);
|
||||
return value;
|
||||
}
|
||||
|
||||
async keys() {
|
||||
return this.values.keys();
|
||||
}
|
||||
}
|
||||
123
node_modules/@weborigami/async-tree/src/drivers/SiteTree.js
generated
vendored
Normal file
123
node_modules/@weborigami/async-tree/src/drivers/SiteTree.js
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
import { setParent } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* A tree of values obtained via HTTP/HTTPS calls. These values will be strings
|
||||
* for HTTP responses with a MIME text type; otherwise they will be ArrayBuffer
|
||||
* instances.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @implements {AsyncTree}
|
||||
*/
|
||||
export default class SiteTree {
|
||||
/**
|
||||
* @param {string} href
|
||||
*/
|
||||
constructor(href = window?.location.href) {
|
||||
if (href?.startsWith(".") && window?.location !== undefined) {
|
||||
// URL represents a relative path; concatenate with current location.
|
||||
href = new URL(href, window.location.href).href;
|
||||
}
|
||||
|
||||
// Add trailing slash if not present; URL should represent a directory.
|
||||
href = trailingSlash.add(href);
|
||||
|
||||
this.href = href;
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
/** @returns {Promise<any>} */
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key.
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
|
||||
// A key with a trailing slash and no extension is for a folder; return a
|
||||
// subtree without making a network request.
|
||||
if (trailingSlash.has(key) && !key.includes(".")) {
|
||||
const href = new URL(key, this.href).href;
|
||||
const value = Reflect.construct(this.constructor, [href]);
|
||||
setParent(value, this);
|
||||
return value;
|
||||
}
|
||||
|
||||
// HACK: For now we don't allow lookup of Origami extension handlers.
|
||||
if (key.endsWith(".handler")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const href = new URL(key, this.href).href;
|
||||
|
||||
// Fetch the data at the given route.
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(href);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.processResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty set of keys.
|
||||
*
|
||||
* For a variation of `SiteTree` that can return the keys for a site route,
|
||||
* see [ExplorableSiteTree](ExplorableSiteTree.html).
|
||||
*
|
||||
* @returns {Promise<Iterable<string>>}
|
||||
*/
|
||||
async keys() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Return true if the given media type is a standard text type.
|
||||
static mediaTypeIsText(mediaType) {
|
||||
if (!mediaType) {
|
||||
return false;
|
||||
}
|
||||
const regex = /^(?<type>[^/]+)\/(?<subtype>[^;]+)/;
|
||||
const match = mediaType.match(regex);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
const { type, subtype } = match.groups;
|
||||
if (type === "text") {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
subtype === "json" ||
|
||||
subtype.endsWith("+json") ||
|
||||
subtype.endsWith(".json") ||
|
||||
subtype === "xml" ||
|
||||
subtype.endsWith("+xml") ||
|
||||
subtype.endsWith(".xml")
|
||||
);
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.href;
|
||||
}
|
||||
|
||||
processResponse(response) {
|
||||
if (!response.ok) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mediaType = response.headers?.get("Content-Type");
|
||||
if (SiteTree.mediaTypeIsText(mediaType)) {
|
||||
return response.text();
|
||||
} else {
|
||||
const buffer = response.arrayBuffer();
|
||||
setParent(buffer, this);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
get url() {
|
||||
return new URL(this.href);
|
||||
}
|
||||
}
|
||||
174
node_modules/@weborigami/async-tree/src/drivers/calendarTree.js
generated
vendored
Normal file
174
node_modules/@weborigami/async-tree/src/drivers/calendarTree.js
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* Return a tree of years, months, and days from a start date to an end date.
|
||||
*
|
||||
* Both the start and end date can be provided in "YYYY-MM-DD", "YYYY-MM", or
|
||||
* "YYYY" format. If a start year is provided, but a month is not, then the
|
||||
* first month of the year will be used; if a start month is provided, but a day
|
||||
* is not, then the first day of that month will be used. Similar logic applies
|
||||
* to the end date, using the last month of the year or the last day of the
|
||||
* month.
|
||||
*
|
||||
* If a start date is omitted, today will be used, likewise for the end date.
|
||||
*
|
||||
* @typedef {string|undefined} CalendarOptionsDate
|
||||
* @typedef {( year: string, month: string, day: string ) => any} CalendarOptionsFn
|
||||
* @param {{ end?: CalendarOptionsDate, start?: CalendarOptionsDate, value: CalendarOptionsFn }} options
|
||||
*/
|
||||
export default function calendarTree(options) {
|
||||
const start = dateParts(options.start);
|
||||
const end = dateParts(options.end);
|
||||
const valueFn = options.value;
|
||||
|
||||
// Fill in the missing parts of the start and end dates.
|
||||
const today = new Date();
|
||||
|
||||
if (start.day === undefined) {
|
||||
start.day = start.year ? 1 : today.getDate();
|
||||
}
|
||||
if (start.month === undefined) {
|
||||
start.month = start.year ? 1 : today.getMonth() + 1;
|
||||
}
|
||||
if (start.year === undefined) {
|
||||
start.year = today.getFullYear();
|
||||
}
|
||||
|
||||
if (end.day === undefined) {
|
||||
end.day = end.month
|
||||
? daysInMonth(end.year, end.month)
|
||||
: end.year
|
||||
? 31 // Last day of December
|
||||
: today.getDate();
|
||||
}
|
||||
if (end.month === undefined) {
|
||||
end.month = end.year ? 12 : today.getMonth() + 1;
|
||||
}
|
||||
if (end.year === undefined) {
|
||||
end.year = today.getFullYear();
|
||||
}
|
||||
|
||||
return yearsTree(start, end, valueFn);
|
||||
}
|
||||
|
||||
function dateParts(date) {
|
||||
let year;
|
||||
let month;
|
||||
let day;
|
||||
if (typeof date === "string") {
|
||||
const parts = date.split("-");
|
||||
year = parts[0] ? parseInt(parts[0]) : undefined;
|
||||
month = parts[1] ? parseInt(parts[1]) : undefined;
|
||||
day = parts[2] ? parseInt(parts[2]) : undefined;
|
||||
}
|
||||
return { year, month, day };
|
||||
}
|
||||
|
||||
function daysForMonthTree(year, month, start, end, valueFn) {
|
||||
return {
|
||||
async get(day) {
|
||||
day = parseInt(trailingSlash.remove(day));
|
||||
return this.inRange(day)
|
||||
? valueFn(year.toString(), twoDigits(month), twoDigits(day))
|
||||
: undefined;
|
||||
},
|
||||
|
||||
inRange(day) {
|
||||
if (year === start.year && year === end.year) {
|
||||
if (month === start.month && month === end.month) {
|
||||
return day >= start.day && day <= end.day;
|
||||
} else if (month === start.month) {
|
||||
return day >= start.day;
|
||||
} else if (month === end.month) {
|
||||
return day <= end.day;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else if (year === start.year) {
|
||||
if (month === start.month) {
|
||||
return day >= start.day;
|
||||
} else {
|
||||
return month > start.month;
|
||||
}
|
||||
} else if (year === end.year) {
|
||||
if (month === end.month) {
|
||||
return day <= end.day;
|
||||
} else {
|
||||
return month < end.month;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const days = Array.from(
|
||||
{ length: daysInMonth(year, month) },
|
||||
(_, i) => i + 1
|
||||
);
|
||||
return days
|
||||
.filter((day) => this.inRange(day))
|
||||
.map((day) => twoDigits(day));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function daysInMonth(year, month) {
|
||||
return new Date(year, month, 0).getDate();
|
||||
}
|
||||
|
||||
function monthsForYearTree(year, start, end, valueFn) {
|
||||
return {
|
||||
async get(month) {
|
||||
month = parseInt(trailingSlash.remove(month));
|
||||
return this.inRange(month)
|
||||
? daysForMonthTree(year, month, start, end, valueFn)
|
||||
: undefined;
|
||||
},
|
||||
|
||||
inRange(month) {
|
||||
if (year === start.year && year === end.year) {
|
||||
return month >= start.month && month <= end.month;
|
||||
} else if (year === start.year) {
|
||||
return month >= start.month;
|
||||
} else if (year === end.year) {
|
||||
return month <= end.month;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
return months
|
||||
.filter((month) => this.inRange(month))
|
||||
.map((month) => twoDigits(month));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function twoDigits(number) {
|
||||
return number.toString().padStart(2, "0");
|
||||
}
|
||||
|
||||
function yearsTree(start, end, valueFn) {
|
||||
return {
|
||||
async get(year) {
|
||||
year = parseInt(trailingSlash.remove(year));
|
||||
return this.inRange(year)
|
||||
? monthsForYearTree(year, start, end, valueFn)
|
||||
: undefined;
|
||||
},
|
||||
|
||||
inRange(year) {
|
||||
return year >= start.year && year <= end.year;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
return Array.from(
|
||||
{ length: end.year - start.year + 1 },
|
||||
(_, i) => start.year + i
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
140
node_modules/@weborigami/async-tree/src/extension.js
generated
vendored
Normal file
140
node_modules/@weborigami/async-tree/src/extension.js
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
import * as trailingSlash from "./trailingSlash.js";
|
||||
import { isStringLike, toString } from "./utilities.js";
|
||||
|
||||
/**
|
||||
* Replicate the logic of Node POSIX path.extname at
|
||||
* https://github.com/nodejs/node/blob/main/lib/path.js so that we can use this
|
||||
* in the browser.
|
||||
*
|
||||
* @param {string} path
|
||||
* @returns {string}
|
||||
*/
|
||||
export function extname(path) {
|
||||
if (typeof path !== "string") {
|
||||
throw new TypeError(`Expected a string, got ${typeof path}`);
|
||||
}
|
||||
let startDot = -1;
|
||||
let startPart = 0;
|
||||
let end = -1;
|
||||
let matchedSlash = true;
|
||||
// Track the state of characters (if any) we see before our first dot and
|
||||
// after any path separator we find
|
||||
let preDotState = 0;
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
const char = path[i];
|
||||
if (char === "/") {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
startPart = i + 1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// extension
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
if (char === ".") {
|
||||
// If this is our first dot, mark it as the start of our extension
|
||||
if (startDot === -1) startDot = i;
|
||||
else if (preDotState !== 1) preDotState = 1;
|
||||
} else if (startDot !== -1) {
|
||||
// We saw a non-dot and non-path separator before our dot, so we should
|
||||
// have a good chance at having a non-empty extension
|
||||
preDotState = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
startDot === -1 ||
|
||||
end === -1 ||
|
||||
// We saw a non-dot character immediately before the dot
|
||||
preDotState === 0 ||
|
||||
// The (right-most) trimmed path component is exactly '..'
|
||||
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
return path.slice(startDot, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the key ends with the given extension. If it does, return the base
|
||||
* name without the extension; if it doesn't return null.
|
||||
*
|
||||
* If the extension is empty, the key must not have an extension to match.
|
||||
*
|
||||
* If the extension is a slash, then the key must end with a slash for the match
|
||||
* to succeed. Otherwise, a trailing slash in the key is ignored for purposes of
|
||||
* comparison to comply with the way Origami can unpack files. Example: the keys
|
||||
* "data.json" and "data.json/" are treated equally.
|
||||
*
|
||||
* This uses a different, more general interpretation of "extension" to mean any
|
||||
* suffix, rather than Node's interpretation in `extname`. In particular, this
|
||||
* will match a multi-part extension like ".foo.bar" that contains more than one
|
||||
* dot.
|
||||
*/
|
||||
export function match(key, ext) {
|
||||
if (!isStringLike(key)) {
|
||||
return null;
|
||||
}
|
||||
key = toString(key);
|
||||
|
||||
if (ext === "/") {
|
||||
return trailingSlash.has(key) ? trailingSlash.remove(key) : null;
|
||||
}
|
||||
|
||||
// Key matches if it ends with the same extension
|
||||
const normalized = trailingSlash.remove(key);
|
||||
if (normalized.endsWith(ext)) {
|
||||
const removed =
|
||||
ext.length > 0 ? normalized.slice(0, -ext.length) : normalized;
|
||||
return trailingSlash.toggle(removed, trailingSlash.has(key));
|
||||
}
|
||||
|
||||
// Didn't match
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given key ends in the source extension (which will generally include a
|
||||
* period), replace that extension with the result extension (which again should
|
||||
* generally include a period). Otherwise, return the key as is.
|
||||
*
|
||||
* If the key ends in a trailing slash, that will be preserved in the result.
|
||||
* Exception: if the source extension is empty, and the key doesn't have an
|
||||
* extension, the result extension will be appended to the key without a slash.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} sourceExtension
|
||||
* @param {string} resultExtension
|
||||
*/
|
||||
export function replace(key, sourceExtension, resultExtension) {
|
||||
if (!isStringLike(key)) {
|
||||
return null;
|
||||
}
|
||||
key = toString(key);
|
||||
|
||||
if (!match(key, sourceExtension)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
let replaced;
|
||||
const normalizedKey = trailingSlash.remove(key);
|
||||
if (sourceExtension === "") {
|
||||
replaced = normalizedKey + resultExtension;
|
||||
if (!normalizedKey.includes(".")) {
|
||||
return replaced;
|
||||
}
|
||||
} else if (sourceExtension === "/") {
|
||||
return trailingSlash.remove(key) + resultExtension;
|
||||
} else {
|
||||
replaced =
|
||||
normalizedKey.slice(0, -sourceExtension.length) + resultExtension;
|
||||
}
|
||||
|
||||
return trailingSlash.toggle(replaced, trailingSlash.has(key));
|
||||
}
|
||||
16
node_modules/@weborigami/async-tree/src/internal.js
generated
vendored
Normal file
16
node_modules/@weborigami/async-tree/src/internal.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// This library includes a number of modules with circular dependencies. This
|
||||
// module exists to explicitly set the loading order for those modules. To
|
||||
// enforce use of this loading order, other modules should only load the modules
|
||||
// below via this module.
|
||||
//
|
||||
// About this pattern:
|
||||
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
||||
//
|
||||
// Note: to avoid having VS Code auto-sort the imports, keep lines between them.
|
||||
|
||||
export * as Tree from "./Tree.js";
|
||||
|
||||
export { default as ObjectTree } from "./drivers/ObjectTree.js";
|
||||
|
||||
export { default as DeepObjectTree } from "./drivers/DeepObjectTree.js";
|
||||
4
node_modules/@weborigami/async-tree/src/jsonKeys.d.ts
generated
vendored
Normal file
4
node_modules/@weborigami/async-tree/src/jsonKeys.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Treelike } from "../index.ts";
|
||||
|
||||
export function parse(json: string): any;
|
||||
export function stringify(treelike: Treelike): Promise<string>;
|
||||
23
node_modules/@weborigami/async-tree/src/jsonKeys.js
generated
vendored
Normal file
23
node_modules/@weborigami/async-tree/src/jsonKeys.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Tree } from "./internal.js";
|
||||
|
||||
/**
|
||||
* The JSON Keys protocol lets a site expose the keys of a node in the site so
|
||||
* that they can be read by SiteTree.
|
||||
*
|
||||
* This file format is a JSON array of key descriptors: a string like
|
||||
* "index.html" for a specific resource available at the node, or a string with
|
||||
* a trailing slash like "about/" for a subtree of that node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given a tree node, return a JSON string that can be written to a .keys.json
|
||||
* file.
|
||||
*/
|
||||
export async function stringify(treelike) {
|
||||
const tree = Tree.from(treelike);
|
||||
let keys = Array.from(await tree.keys());
|
||||
// Skip the key `.keys.json` if present.
|
||||
keys = keys.filter((key) => key !== ".keys.json");
|
||||
const json = JSON.stringify(keys);
|
||||
return json;
|
||||
}
|
||||
98
node_modules/@weborigami/async-tree/src/operations/cache.js
generated
vendored
Normal file
98
node_modules/@weborigami/async-tree/src/operations/cache.js
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ObjectTree, Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Caches values from a source tree in a second cache tree. Cache source tree
|
||||
* keys in memory.
|
||||
*
|
||||
* If no second tree is supplied, an in-memory value cache is used.
|
||||
*
|
||||
* An optional third filter tree can be supplied. If a filter tree is supplied,
|
||||
* only values for keys that match the filter will be cached.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
||||
* @typedef {import("../../index.ts").Treelike} Treelike
|
||||
*
|
||||
* @param {Treelike} sourceTreelike
|
||||
* @param {AsyncMutableTree} [cacheTreelike]
|
||||
* @param {Treelike} [filterTreelike]
|
||||
* @returns {AsyncTree & { description: string }}
|
||||
*/
|
||||
export default function treeCache(
|
||||
sourceTreelike,
|
||||
cacheTreelike,
|
||||
filterTreelike
|
||||
) {
|
||||
if (!sourceTreelike) {
|
||||
const error = new TypeError(`cache: The source tree isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const source = Tree.from(sourceTreelike);
|
||||
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
||||
|
||||
/** @type {AsyncMutableTree} */
|
||||
let cache;
|
||||
if (cacheTreelike) {
|
||||
// @ts-ignore
|
||||
cache = Tree.from(cacheTreelike);
|
||||
if (!Tree.isAsyncMutableTree(cache)) {
|
||||
throw new Error("Cache tree must define a set() method.");
|
||||
}
|
||||
} else {
|
||||
cache = new ObjectTree({});
|
||||
}
|
||||
|
||||
let keys;
|
||||
return {
|
||||
description: "cache",
|
||||
|
||||
async get(key) {
|
||||
// Check cache tree first.
|
||||
let cacheValue = await cache.get(key);
|
||||
if (cacheValue !== undefined && !Tree.isAsyncTree(cacheValue)) {
|
||||
// Leaf node cache hit
|
||||
return cacheValue;
|
||||
}
|
||||
|
||||
// Cache miss or interior node cache hit.
|
||||
let value = await source.get(key);
|
||||
if (value !== undefined) {
|
||||
// If a filter is defined, does the key match the filter?
|
||||
const filterValue = filter ? await filter.get(key) : undefined;
|
||||
const filterMatch = !filter || filterValue !== undefined;
|
||||
if (filterMatch) {
|
||||
if (Tree.isAsyncTree(value)) {
|
||||
// Construct merged tree for a tree result.
|
||||
if (cacheValue === undefined) {
|
||||
// Construct new empty container in cache
|
||||
await cache.set(key, {});
|
||||
cacheValue = await cache.get(key);
|
||||
if (!Tree.isAsyncTree(cacheValue)) {
|
||||
// Coerce to tree and then save it back to the cache. This is
|
||||
// necessary, e.g., if cache is an ObjectTree; we want the
|
||||
// subtree to also be an ObjectTree, not a plain object.
|
||||
cacheValue = Tree.from(cacheValue);
|
||||
await cache.set(key, cacheValue);
|
||||
}
|
||||
}
|
||||
value = treeCache(value, cacheValue, filterValue);
|
||||
} else {
|
||||
// Save in cache before returning.
|
||||
await cache.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
keys ??= await source.keys();
|
||||
return keys;
|
||||
},
|
||||
};
|
||||
}
|
||||
124
node_modules/@weborigami/async-tree/src/operations/cachedKeyFunctions.js
generated
vendored
Normal file
124
node_modules/@weborigami/async-tree/src/operations/cachedKeyFunctions.js
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
const treeToCaches = new WeakMap();
|
||||
|
||||
/**
|
||||
* Given a key function, return a new key function and inverse key function that
|
||||
* cache the results of the original.
|
||||
*
|
||||
* If `skipSubtrees` is true, the inverse key function will skip any source keys
|
||||
* that are keys for subtrees, returning the source key unmodified.
|
||||
*
|
||||
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
||||
*
|
||||
* @param {KeyFn} keyFn
|
||||
* @param {boolean?} skipSubtrees
|
||||
* @returns {{ key: KeyFn, inverseKey: KeyFn }}
|
||||
*/
|
||||
export default function cachedKeyFunctions(keyFn, skipSubtrees = false) {
|
||||
return {
|
||||
async inverseKey(resultKey, tree) {
|
||||
const { resultKeyToSourceKey, sourceKeyToResultKey } =
|
||||
getKeyMapsForTree(tree);
|
||||
|
||||
const cachedSourceKey = searchKeyMap(resultKeyToSourceKey, resultKey);
|
||||
if (cachedSourceKey !== undefined) {
|
||||
return cachedSourceKey;
|
||||
}
|
||||
|
||||
// Iterate through the tree's keys, calculating source keys as we go,
|
||||
// until we find a match. Cache all the intermediate results and the
|
||||
// final match. This is O(n), but we stop as soon as we find a match,
|
||||
// and subsequent calls will benefit from the intermediate results.
|
||||
const resultKeyWithoutSlash = trailingSlash.remove(resultKey);
|
||||
for (const sourceKey of await tree.keys()) {
|
||||
// Skip any source keys we already know about.
|
||||
if (sourceKeyToResultKey.has(sourceKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const computedResultKey = await computeAndCacheResultKey(
|
||||
tree,
|
||||
keyFn,
|
||||
skipSubtrees,
|
||||
sourceKey
|
||||
);
|
||||
|
||||
if (
|
||||
computedResultKey &&
|
||||
trailingSlash.remove(computedResultKey) === resultKeyWithoutSlash
|
||||
) {
|
||||
// Match found, match trailing slash and return
|
||||
return trailingSlash.toggle(sourceKey, trailingSlash.has(resultKey));
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async key(sourceKey, tree) {
|
||||
const { sourceKeyToResultKey } = getKeyMapsForTree(tree);
|
||||
|
||||
const cachedResultKey = searchKeyMap(sourceKeyToResultKey, sourceKey);
|
||||
if (cachedResultKey !== undefined) {
|
||||
return cachedResultKey;
|
||||
}
|
||||
|
||||
const resultKey = await computeAndCacheResultKey(
|
||||
tree,
|
||||
keyFn,
|
||||
skipSubtrees,
|
||||
sourceKey
|
||||
);
|
||||
return resultKey;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function computeAndCacheResultKey(tree, keyFn, skipSubtrees, sourceKey) {
|
||||
const { resultKeyToSourceKey, sourceKeyToResultKey } =
|
||||
getKeyMapsForTree(tree);
|
||||
|
||||
const resultKey =
|
||||
skipSubtrees && trailingSlash.has(sourceKey)
|
||||
? sourceKey
|
||||
: await keyFn(sourceKey, tree);
|
||||
|
||||
sourceKeyToResultKey.set(sourceKey, resultKey);
|
||||
resultKeyToSourceKey.set(resultKey, sourceKey);
|
||||
|
||||
return resultKey;
|
||||
}
|
||||
|
||||
// Maintain key->inverseKey and inverseKey->key mappings for each tree. These
|
||||
// store subtree keys in either direction with a trailing slash.
|
||||
function getKeyMapsForTree(tree) {
|
||||
let keyMaps = treeToCaches.get(tree);
|
||||
if (!keyMaps) {
|
||||
keyMaps = {
|
||||
resultKeyToSourceKey: new Map(),
|
||||
sourceKeyToResultKey: new Map(),
|
||||
};
|
||||
treeToCaches.set(tree, keyMaps);
|
||||
}
|
||||
return keyMaps;
|
||||
}
|
||||
|
||||
// Search the given key map for the key. Ignore trailing slashes in the search,
|
||||
// but preserve them in the result.
|
||||
function searchKeyMap(keyMap, key) {
|
||||
// Check key as is
|
||||
let match;
|
||||
if (keyMap.has(key)) {
|
||||
match = keyMap.get(key);
|
||||
} else if (!trailingSlash.has(key)) {
|
||||
// Check key without trailing slash
|
||||
const withSlash = trailingSlash.add(key);
|
||||
if (keyMap.has(withSlash)) {
|
||||
match = keyMap.get(withSlash);
|
||||
}
|
||||
}
|
||||
return match
|
||||
? trailingSlash.toggle(match, trailingSlash.has(key))
|
||||
: undefined;
|
||||
}
|
||||
34
node_modules/@weborigami/async-tree/src/operations/concat.js
generated
vendored
Normal file
34
node_modules/@weborigami/async-tree/src/operations/concat.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { toString } from "../utilities.js";
|
||||
import deepValuesIterator from "./deepValuesIterator.js";
|
||||
|
||||
/**
|
||||
* Concatenate the deep text values in a tree.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
*
|
||||
* @this {AsyncTree|null}
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
*/
|
||||
export default async function concatTreeValues(treelike) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`concat: The tree isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const strings = [];
|
||||
for await (const value of deepValuesIterator(treelike, { expand: true })) {
|
||||
let string;
|
||||
if (value === null) {
|
||||
console.warn("Warning: Origami template encountered a null value");
|
||||
string = "null";
|
||||
} else if (value === undefined) {
|
||||
console.warn("Warning: Origami template encountered an undefined value");
|
||||
string = "undefined";
|
||||
} else {
|
||||
string = toString(value);
|
||||
}
|
||||
strings.push(string);
|
||||
}
|
||||
return strings.join("");
|
||||
}
|
||||
77
node_modules/@weborigami/async-tree/src/operations/deepMerge.js
generated
vendored
Normal file
77
node_modules/@weborigami/async-tree/src/operations/deepMerge.js
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* Return a tree that performs a deep merge of the given trees.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @param {import("../../index.ts").Treelike[]} sources
|
||||
* @returns {AsyncTree & { description: string }}
|
||||
*/
|
||||
export default function deepMerge(...sources) {
|
||||
let trees = sources.map((treelike) => Tree.from(treelike, { deep: true }));
|
||||
let mergeParent;
|
||||
return {
|
||||
description: "deepMerge",
|
||||
|
||||
async get(key) {
|
||||
const subtrees = [];
|
||||
|
||||
// Check trees for the indicated key in reverse order.
|
||||
for (let index = trees.length - 1; index >= 0; index--) {
|
||||
const tree = trees[index];
|
||||
const value = await tree.get(key);
|
||||
if (Tree.isAsyncTree(value)) {
|
||||
if (value.parent === tree) {
|
||||
// Merged tree acts as parent instead of the source tree.
|
||||
value.parent = this;
|
||||
}
|
||||
subtrees.unshift(value);
|
||||
} else if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (subtrees.length > 1) {
|
||||
const merged = deepMerge(...subtrees);
|
||||
merged.parent = this;
|
||||
return merged;
|
||||
} else if (subtrees.length === 1) {
|
||||
return subtrees[0];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const keys = new Set();
|
||||
// Collect keys in the order the trees were provided.
|
||||
for (const tree of trees) {
|
||||
for (const key of await tree.keys()) {
|
||||
// Remove the alternate form of the key (if it exists)
|
||||
const alternateKey = trailingSlash.toggle(key);
|
||||
if (alternateKey !== key) {
|
||||
keys.delete(alternateKey);
|
||||
}
|
||||
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
|
||||
get parent() {
|
||||
return mergeParent;
|
||||
},
|
||||
set parent(parent) {
|
||||
mergeParent = parent;
|
||||
trees = sources.map((treelike) => {
|
||||
const tree = Tree.isAsyncTree(treelike)
|
||||
? Object.create(treelike)
|
||||
: Tree.from(treelike);
|
||||
tree.parent = parent;
|
||||
return tree;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
37
node_modules/@weborigami/async-tree/src/operations/deepReverse.js
generated
vendored
Normal file
37
node_modules/@weborigami/async-tree/src/operations/deepReverse.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Reverse the order of keys at all levels of the tree.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Treelike} Treelike
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @returns {AsyncTree}
|
||||
*/
|
||||
export default function deepReverse(treelike) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(
|
||||
`deepReverse: The tree to reverse isn't defined.`
|
||||
);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike, { deep: true });
|
||||
return {
|
||||
async get(key) {
|
||||
let value = await tree.get(key);
|
||||
if (Tree.isAsyncTree(value)) {
|
||||
value = deepReverse(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const keys = Array.from(await tree.keys());
|
||||
keys.reverse();
|
||||
return keys;
|
||||
},
|
||||
};
|
||||
}
|
||||
42
node_modules/@weborigami/async-tree/src/operations/deepTake.js
generated
vendored
Normal file
42
node_modules/@weborigami/async-tree/src/operations/deepTake.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Returns a function that traverses a tree deeply and returns the values of the
|
||||
* first `count` keys.
|
||||
*
|
||||
* This is similar to `deepValues`, but it is more efficient for large trees as
|
||||
* stops after `count` values.
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {number} count
|
||||
*/
|
||||
export default async function deepTake(treelike, count) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`deepTake: The tree isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = await Tree.from(treelike, { deep: true });
|
||||
const { values } = await traverse(tree, count);
|
||||
return Tree.from(values, { deep: true });
|
||||
}
|
||||
|
||||
async function traverse(tree, count) {
|
||||
const values = [];
|
||||
for (const key of await tree.keys()) {
|
||||
if (count <= 0) {
|
||||
break;
|
||||
}
|
||||
let value = await tree.get(key);
|
||||
if (Tree.isAsyncTree(value)) {
|
||||
const traversed = await traverse(value, count);
|
||||
values.push(...traversed.values);
|
||||
count = traversed.count;
|
||||
} else {
|
||||
values.push(value);
|
||||
count--;
|
||||
}
|
||||
}
|
||||
return { count, values };
|
||||
}
|
||||
19
node_modules/@weborigami/async-tree/src/operations/deepValues.js
generated
vendored
Normal file
19
node_modules/@weborigami/async-tree/src/operations/deepValues.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import deepValuesIterator from "./deepValuesIterator.js";
|
||||
|
||||
/**
|
||||
* Return the in-order exterior values of a tree as a flat array.
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {{ expand?: boolean }} [options]
|
||||
*/
|
||||
export default async function deepValues(
|
||||
treelike,
|
||||
options = { expand: false }
|
||||
) {
|
||||
const iterator = deepValuesIterator(treelike, options);
|
||||
const values = [];
|
||||
for await (const value of iterator) {
|
||||
values.push(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
37
node_modules/@weborigami/async-tree/src/operations/deepValuesIterator.js
generated
vendored
Normal file
37
node_modules/@weborigami/async-tree/src/operations/deepValuesIterator.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Return an iterator that yields all values in a tree, including nested trees.
|
||||
*
|
||||
* If the `expand` option is true, treelike values (but not functions) will be
|
||||
* expanded into nested trees and their values will be yielded.
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {{ expand?: boolean }} [options]
|
||||
* @returns {AsyncGenerator<any, void, undefined>}
|
||||
*/
|
||||
export default async function* deepValuesIterator(
|
||||
treelike,
|
||||
options = { expand: false }
|
||||
) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`deepValues: The tree isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike, { deep: true });
|
||||
for (const key of await tree.keys()) {
|
||||
let value = await tree.get(key);
|
||||
|
||||
// Recurse into child trees, but don't expand functions.
|
||||
const recurse =
|
||||
Tree.isAsyncTree(value) ||
|
||||
(options.expand && typeof value !== "function" && Tree.isTreelike(value));
|
||||
if (recurse) {
|
||||
yield* deepValuesIterator(value, options);
|
||||
} else {
|
||||
yield value;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
node_modules/@weborigami/async-tree/src/operations/group.js
generated
vendored
Normal file
53
node_modules/@weborigami/async-tree/src/operations/group.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import { ObjectTree, Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Given a function that returns a grouping key for a value, returns a transform
|
||||
* that applies that grouping function to a tree.
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {import("../../index.ts").ValueKeyFn} groupKeyFn
|
||||
*/
|
||||
export default async function group(treelike, groupKeyFn) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`groupBy: The tree to group isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
|
||||
const keys = Array.from(await tree.keys());
|
||||
|
||||
// Are all the keys integers?
|
||||
const isArray = keys.every((key) => !Number.isNaN(parseInt(key)));
|
||||
|
||||
const result = {};
|
||||
for (const key of await tree.keys()) {
|
||||
const value = await tree.get(key);
|
||||
|
||||
// Get the groups for this value.
|
||||
let groups = await groupKeyFn(value, key, tree);
|
||||
if (!groups) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Tree.isTreelike(groups)) {
|
||||
// A single value was returned
|
||||
groups = [groups];
|
||||
}
|
||||
groups = Tree.from(groups);
|
||||
|
||||
// Add the value to each group.
|
||||
for (const group of await Tree.values(groups)) {
|
||||
if (isArray) {
|
||||
result[group] ??= [];
|
||||
result[group].push(value);
|
||||
} else {
|
||||
result[group] ??= {};
|
||||
result[group][key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ObjectTree(result);
|
||||
}
|
||||
20
node_modules/@weborigami/async-tree/src/operations/invokeFunctions.js
generated
vendored
Normal file
20
node_modules/@weborigami/async-tree/src/operations/invokeFunctions.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
export default function invokeFunctions(treelike) {
|
||||
const tree = Tree.from(treelike);
|
||||
return {
|
||||
async get(key) {
|
||||
let value = await tree.get(key);
|
||||
if (typeof value === "function") {
|
||||
value = value();
|
||||
} else if (Tree.isAsyncTree(value)) {
|
||||
value = invokeFunctions(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
return tree.keys();
|
||||
},
|
||||
};
|
||||
}
|
||||
48
node_modules/@weborigami/async-tree/src/operations/keyFunctionsForExtensions.js
generated
vendored
Normal file
48
node_modules/@weborigami/async-tree/src/operations/keyFunctionsForExtensions.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as extension from "../extension.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* Given a source resultExtension and a result resultExtension, return a pair of key
|
||||
* functions that map between them.
|
||||
*
|
||||
* The resulting `inverseKey` and `key` functions are compatible with those
|
||||
* expected by map and other transforms.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @param {{ resultExtension?: string, sourceExtension: string }}
|
||||
* options
|
||||
*/
|
||||
export default function keyFunctionsForExtensions({
|
||||
resultExtension,
|
||||
sourceExtension,
|
||||
}) {
|
||||
if (resultExtension === undefined) {
|
||||
resultExtension = sourceExtension;
|
||||
}
|
||||
|
||||
checkDeprecatedExtensionWithoutDot(resultExtension);
|
||||
checkDeprecatedExtensionWithoutDot(sourceExtension);
|
||||
|
||||
return {
|
||||
async inverseKey(resultKey, tree) {
|
||||
// Remove trailing slash so that mapFn won't inadvertently unpack files.
|
||||
const baseKey = trailingSlash.remove(resultKey);
|
||||
const basename = extension.match(baseKey, resultExtension);
|
||||
return basename ? `${basename}${sourceExtension}` : undefined;
|
||||
},
|
||||
|
||||
async key(sourceKey, tree) {
|
||||
return extension.match(sourceKey, sourceExtension)
|
||||
? extension.replace(sourceKey, sourceExtension, resultExtension)
|
||||
: undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function checkDeprecatedExtensionWithoutDot(extension) {
|
||||
if (extension && extension !== "/" && !extension.startsWith(".")) {
|
||||
throw new RangeError(
|
||||
`map: Warning: the extension "${extension}" should start with a period.`
|
||||
);
|
||||
}
|
||||
}
|
||||
129
node_modules/@weborigami/async-tree/src/operations/map.js
generated
vendored
Normal file
129
node_modules/@weborigami/async-tree/src/operations/map.js
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* Transform the keys and/or values of a tree.
|
||||
*
|
||||
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
||||
* @typedef {import("../../index.ts").TreeMapOptions} MapOptions
|
||||
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {MapOptions|ValueKeyFn} options
|
||||
*/
|
||||
export default function map(treelike, options = {}) {
|
||||
let deep;
|
||||
let description;
|
||||
let inverseKeyFn;
|
||||
let keyFn;
|
||||
let needsSourceValue;
|
||||
let valueFn;
|
||||
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`map: The tree to map isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (typeof options === "function") {
|
||||
// Take the single function argument as the valueFn
|
||||
valueFn = options;
|
||||
} else {
|
||||
deep = options.deep;
|
||||
description = options.description;
|
||||
inverseKeyFn = options.inverseKey;
|
||||
keyFn = options.key;
|
||||
needsSourceValue = options.needsSourceValue;
|
||||
valueFn = options.value;
|
||||
}
|
||||
|
||||
deep ??= false;
|
||||
description ??= "key/value map";
|
||||
// @ts-ignore
|
||||
inverseKeyFn ??= valueFn?.inverseKey;
|
||||
// @ts-ignore
|
||||
keyFn ??= valueFn?.key;
|
||||
needsSourceValue ??= true;
|
||||
|
||||
if ((keyFn && !inverseKeyFn) || (!keyFn && inverseKeyFn)) {
|
||||
throw new TypeError(
|
||||
`map: You must specify both key and inverseKey functions, or neither.`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@weborigami/types").AsyncTree} tree
|
||||
*/
|
||||
function mapFn(tree) {
|
||||
// The transformed tree is actually an extension of the original tree's
|
||||
// prototype chain. This allows the transformed tree to inherit any
|
||||
// properties/methods. For example, the `parent` of the transformed tree is
|
||||
// the original tree's parent.
|
||||
const transformed = Object.create(tree);
|
||||
|
||||
transformed.description = description;
|
||||
|
||||
if (keyFn || valueFn) {
|
||||
transformed.get = async (resultKey) => {
|
||||
// Step 1: Map the result key to the source key.
|
||||
const sourceKey = (await inverseKeyFn?.(resultKey, tree)) ?? resultKey;
|
||||
|
||||
if (sourceKey === undefined) {
|
||||
// No source key means no value.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Step 2: Get the source value.
|
||||
let sourceValue;
|
||||
if (needsSourceValue) {
|
||||
// Normal case: get the value from the source tree.
|
||||
sourceValue = await tree.get(sourceKey);
|
||||
} else if (deep && trailingSlash.has(sourceKey)) {
|
||||
// Only get the source value if it's expected to be a subtree.
|
||||
sourceValue = tree;
|
||||
}
|
||||
|
||||
// Step 3: Map the source value to the result value.
|
||||
let resultValue;
|
||||
if (needsSourceValue && sourceValue === undefined) {
|
||||
// No source value means no result value.
|
||||
resultValue = undefined;
|
||||
} else if (deep && Tree.isAsyncTree(sourceValue)) {
|
||||
// Map a subtree.
|
||||
resultValue = mapFn(sourceValue);
|
||||
} else if (valueFn) {
|
||||
// Map a single value.
|
||||
resultValue = await valueFn(sourceValue, sourceKey, tree);
|
||||
} else {
|
||||
// Return source value as is.
|
||||
resultValue = sourceValue;
|
||||
}
|
||||
|
||||
return resultValue;
|
||||
};
|
||||
}
|
||||
|
||||
if (keyFn) {
|
||||
transformed.keys = async () => {
|
||||
// Apply the keyFn to source keys for leaf values (not subtrees).
|
||||
const sourceKeys = Array.from(await tree.keys());
|
||||
const mapped = await Promise.all(
|
||||
sourceKeys.map(async (sourceKey) =>
|
||||
// Deep maps leave source keys for subtrees alone
|
||||
deep && trailingSlash.has(sourceKey)
|
||||
? sourceKey
|
||||
: await keyFn(sourceKey, tree)
|
||||
)
|
||||
);
|
||||
// Filter out any cases where the keyFn returned undefined.
|
||||
const resultKeys = mapped.filter((key) => key !== undefined);
|
||||
return resultKeys;
|
||||
};
|
||||
}
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike, { deep });
|
||||
return mapFn(tree);
|
||||
}
|
||||
65
node_modules/@weborigami/async-tree/src/operations/merge.js
generated
vendored
Normal file
65
node_modules/@weborigami/async-tree/src/operations/merge.js
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as symbols from "../symbols.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* Return a tree that performs a shallow merge of the given trees.
|
||||
*
|
||||
* Given a set of trees, the `get` method looks at each tree in turn. The first
|
||||
* tree is asked for the value with the key. If an tree returns a defined value
|
||||
* (i.e., not undefined), that value is returned. If the first tree returns
|
||||
* undefined, the second tree will be asked, and so on. If none of the trees
|
||||
* return a defined value, the `get` method returns undefined.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @param {import("../../index.ts").Treelike[]} sources
|
||||
* @returns {AsyncTree & { description: string, trees: AsyncTree[]}}
|
||||
*/
|
||||
export default function merge(...sources) {
|
||||
const trees = sources.map((treelike) => Tree.from(treelike));
|
||||
return {
|
||||
description: "merge",
|
||||
|
||||
async get(key) {
|
||||
// Check trees for the indicated key in reverse order.
|
||||
for (let index = trees.length - 1; index >= 0; index--) {
|
||||
const tree = trees[index];
|
||||
const value = await tree.get(key);
|
||||
if (value !== undefined) {
|
||||
// Merged tree acts as parent instead of the source tree.
|
||||
if (Tree.isAsyncTree(value) && value.parent === tree) {
|
||||
value.parent = this;
|
||||
} else if (
|
||||
typeof value === "object" &&
|
||||
value?.[symbols.parent] === tree
|
||||
) {
|
||||
value[symbols.parent] = this;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const keys = new Set();
|
||||
// Collect keys in the order the trees were provided.
|
||||
for (const tree of trees) {
|
||||
for (const key of await tree.keys()) {
|
||||
// Remove the alternate form of the key (if it exists)
|
||||
const alternateKey = trailingSlash.toggle(key);
|
||||
if (alternateKey !== key) {
|
||||
keys.delete(alternateKey);
|
||||
}
|
||||
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
|
||||
get trees() {
|
||||
return trees;
|
||||
},
|
||||
};
|
||||
}
|
||||
88
node_modules/@weborigami/async-tree/src/operations/regExpKeys.js
generated
vendored
Normal file
88
node_modules/@weborigami/async-tree/src/operations/regExpKeys.js
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Tree } from "../internal.js";
|
||||
import * as trailingSlash from "../trailingSlash.js";
|
||||
|
||||
/**
|
||||
* A tree whose keys are strings interpreted as regular expressions.
|
||||
*
|
||||
* Requests to `get` a key are matched against the regular expressions, and the
|
||||
* value for the first matching key is returned. The regular expresions are
|
||||
* taken to match the entire key -- if they do not already start and end with
|
||||
* `^` and `$` respectively, those are added.
|
||||
*
|
||||
* @type {import("../../index.ts").TreeTransform}
|
||||
*/
|
||||
export default async function regExpKeys(treelike) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(
|
||||
`regExpKeys: The tree of regular expressions isn't defined.`
|
||||
);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
const map = new Map();
|
||||
|
||||
// We build the output tree first so that we can refer to it when setting
|
||||
// `parent` on subtrees below.
|
||||
let result = {
|
||||
// @ts-ignore
|
||||
description: "regExpKeys",
|
||||
|
||||
async get(key) {
|
||||
if (key == null) {
|
||||
// Reject nullish key.
|
||||
throw new ReferenceError(
|
||||
`${this.constructor.name}: Cannot get a null or undefined key.`
|
||||
);
|
||||
}
|
||||
|
||||
for (const [regExp, value] of map) {
|
||||
if (regExp.test(key)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async keys() {
|
||||
return map.keys();
|
||||
},
|
||||
};
|
||||
|
||||
// Turn the input tree's string keys into regular expressions, then map those
|
||||
// to the corresponding values.
|
||||
for (const key of await tree.keys()) {
|
||||
if (typeof key !== "string") {
|
||||
// Skip non-string keys.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get value.
|
||||
let value = await tree.get(key);
|
||||
|
||||
let regExp;
|
||||
if (trailingSlash.has(key) || Tree.isAsyncTree(value)) {
|
||||
const baseKey = trailingSlash.remove(key);
|
||||
regExp = new RegExp("^" + baseKey + "/?$");
|
||||
// Subtree
|
||||
value = regExpKeys(value);
|
||||
if (!value.parent) {
|
||||
value.parent = result;
|
||||
}
|
||||
} else {
|
||||
// Construct regular expression.
|
||||
let text = key;
|
||||
if (!text.startsWith("^")) {
|
||||
text = "^" + text;
|
||||
}
|
||||
if (!text.endsWith("$")) {
|
||||
text = text + "$";
|
||||
}
|
||||
regExp = new RegExp(text);
|
||||
}
|
||||
map.set(regExp, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
31
node_modules/@weborigami/async-tree/src/operations/reverse.js
generated
vendored
Normal file
31
node_modules/@weborigami/async-tree/src/operations/reverse.js
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Reverse the order of the top-level keys in the tree.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Treelike} Treelike
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @returns {AsyncTree}
|
||||
*/
|
||||
export default function reverse(treelike) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`reverse: The tree to reverse isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
return {
|
||||
async get(key) {
|
||||
return tree.get(key);
|
||||
},
|
||||
|
||||
async keys() {
|
||||
const keys = Array.from(await tree.keys());
|
||||
keys.reverse();
|
||||
return keys;
|
||||
},
|
||||
};
|
||||
}
|
||||
71
node_modules/@weborigami/async-tree/src/operations/scope.js
generated
vendored
Normal file
71
node_modules/@weborigami/async-tree/src/operations/scope.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* A tree's "scope" is the collection of everything in that tree and all of its
|
||||
* ancestors.
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {import("../../index.ts").Treelike} Treelike
|
||||
*
|
||||
* @param {Treelike} treelike
|
||||
* @returns {AsyncTree & {trees: AsyncTree[]}}
|
||||
*/
|
||||
export default function scope(treelike) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`scope: The tree isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
|
||||
return {
|
||||
// Starting with this tree, search up the parent hierarchy.
|
||||
async get(key) {
|
||||
/** @type {AsyncTree|null|undefined} */
|
||||
let current = tree;
|
||||
let value;
|
||||
while (current) {
|
||||
value = await current.get(key);
|
||||
if (value !== undefined) {
|
||||
break;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
// Collect all keys for this tree and all parents
|
||||
async keys() {
|
||||
const keys = new Set();
|
||||
|
||||
/** @type {AsyncTree|null|undefined} */
|
||||
let current = tree;
|
||||
while (current) {
|
||||
for (const key of await current.keys()) {
|
||||
keys.add(key);
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return keys;
|
||||
},
|
||||
|
||||
// Collect all keys for this tree and all parents.
|
||||
//
|
||||
// This method exists for debugging purposes, as it's helpful to be able to
|
||||
// quickly flatten and view the entire scope chain.
|
||||
get trees() {
|
||||
const result = [];
|
||||
|
||||
/** @type {AsyncTree|null|undefined} */
|
||||
let current = tree;
|
||||
while (current) {
|
||||
result.push(current);
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
61
node_modules/@weborigami/async-tree/src/operations/sort.js
generated
vendored
Normal file
61
node_modules/@weborigami/async-tree/src/operations/sort.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Return a new tree with the original's keys sorted. A comparison function can
|
||||
* be provided; by default the keys will be sorted in [natural sort
|
||||
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
||||
*
|
||||
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
||||
* @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
|
||||
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
|
||||
* SortOptions
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {SortOptions} [options]
|
||||
*/
|
||||
export default function sort(treelike, options) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`sort: The tree to sort isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const sortKey = options?.sortKey;
|
||||
let compare = options?.compare;
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
const transformed = Object.create(tree);
|
||||
transformed.keys = async () => {
|
||||
const keys = Array.from(await tree.keys());
|
||||
|
||||
if (sortKey) {
|
||||
// Invoke the async sortKey function to get sort keys.
|
||||
// Create { key, sortKey } tuples.
|
||||
const tuples = await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const sort = await sortKey(key, tree);
|
||||
if (sort === undefined) {
|
||||
throw new Error(
|
||||
`sortKey function returned undefined for key ${key}`
|
||||
);
|
||||
}
|
||||
return { key, sort };
|
||||
})
|
||||
);
|
||||
|
||||
// Wrap the comparison function so it applies to sort keys.
|
||||
const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
||||
const originalCompare = compare ?? defaultCompare;
|
||||
// Sort by the sort key.
|
||||
tuples.sort((a, b) => originalCompare(a.sort, b.sort));
|
||||
// Map back to the original keys.
|
||||
const sorted = tuples.map((pair) => pair.key);
|
||||
return sorted;
|
||||
} else {
|
||||
// Use original keys as sort keys.
|
||||
// If compare is undefined, this uses default sort order.
|
||||
return keys.slice().sort(compare);
|
||||
}
|
||||
};
|
||||
return transformed;
|
||||
}
|
||||
28
node_modules/@weborigami/async-tree/src/operations/take.js
generated
vendored
Normal file
28
node_modules/@weborigami/async-tree/src/operations/take.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Tree } from "../internal.js";
|
||||
|
||||
/**
|
||||
* Returns a new tree with the number of keys limited to the indicated count.
|
||||
*
|
||||
* @param {import("../../index.ts").Treelike} treelike
|
||||
* @param {number} count
|
||||
*/
|
||||
export default function take(treelike, count) {
|
||||
if (!treelike) {
|
||||
const error = new TypeError(`take: The tree to take from isn't defined.`);
|
||||
/** @type {any} */ (error).position = 0;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const tree = Tree.from(treelike);
|
||||
|
||||
return {
|
||||
async keys() {
|
||||
const keys = Array.from(await tree.keys());
|
||||
return keys.slice(0, count);
|
||||
},
|
||||
|
||||
async get(key) {
|
||||
return tree.get(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
2
node_modules/@weborigami/async-tree/src/symbols.js
generated
vendored
Normal file
2
node_modules/@weborigami/async-tree/src/symbols.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export const deep = Symbol("deep");
|
||||
export const parent = Symbol("parent");
|
||||
54
node_modules/@weborigami/async-tree/src/trailingSlash.js
generated
vendored
Normal file
54
node_modules/@weborigami/async-tree/src/trailingSlash.js
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Add a trailing slash to a string key if the value is truthy. If the key
|
||||
* is not a string, it will be returned as is.
|
||||
*
|
||||
* @param {any} key
|
||||
*/
|
||||
export function add(key) {
|
||||
if (key == null) {
|
||||
throw new ReferenceError("trailingSlash: key was undefined");
|
||||
}
|
||||
return typeof key === "string" && !key.endsWith("/") ? `${key}/` : key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the indicated key is a string with a trailing slash,
|
||||
* false otherwise.
|
||||
*
|
||||
* @param {any} key
|
||||
*/
|
||||
export function has(key) {
|
||||
if (key == null) {
|
||||
throw new ReferenceError("trailingSlash: key was undefined");
|
||||
}
|
||||
return typeof key === "string" && key.endsWith("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a trailing slash from a string key.
|
||||
*
|
||||
* @param {any} key
|
||||
*/
|
||||
export function remove(key) {
|
||||
if (key == null) {
|
||||
throw new ReferenceError("trailingSlash: key was undefined");
|
||||
}
|
||||
return typeof key === "string" ? key.replace(/\/$/, "") : key;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the key has a trailing slash, remove it; otherwise add it.
|
||||
*
|
||||
* @param {any} key
|
||||
* @param {boolean} [force]
|
||||
*/
|
||||
export function toggle(key, force = undefined) {
|
||||
if (key == null) {
|
||||
throw new ReferenceError("trailingSlash: key was undefined");
|
||||
}
|
||||
if (typeof key !== "string") {
|
||||
return key;
|
||||
}
|
||||
const addSlash = force ?? !has(key);
|
||||
return addSlash ? add(key) : remove(key);
|
||||
}
|
||||
18
node_modules/@weborigami/async-tree/src/utilities.d.ts
generated
vendored
Normal file
18
node_modules/@weborigami/async-tree/src/utilities.d.ts
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AsyncTree } from "@weborigami/types";
|
||||
import { Packed, PlainObject, StringLike } from "../index.ts";
|
||||
|
||||
export function box(value: any): any;
|
||||
export function castArrayLike(keys: any[], values: any[]): any;
|
||||
export function getRealmObjectPrototype(object: any): any;
|
||||
export const hiddenFileNames: string[];
|
||||
export function isPacked(obj: any): obj is Packed;
|
||||
export function isPlainObject(obj: any): obj is PlainObject;
|
||||
export function isStringLike(obj: any): obj is StringLike;
|
||||
export function isUnpackable(obj): obj is { unpack: () => any };
|
||||
export function keysFromPath(path: string): string[];
|
||||
export const naturalOrder: (a: string, b: string) => number;
|
||||
export function pathFromKeys(keys: string[]): string;
|
||||
export function pipeline(start: any, ...functions: Function[]): Promise<any>;
|
||||
export function setParent(child: any, parent: AsyncTree): void;
|
||||
export function toPlainValue(object: any): Promise<any>;
|
||||
export function toString(object: any): string;
|
||||
374
node_modules/@weborigami/async-tree/src/utilities.js
generated
vendored
Normal file
374
node_modules/@weborigami/async-tree/src/utilities.js
generated
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
import { Tree } from "./internal.js";
|
||||
import * as symbols from "./symbols.js";
|
||||
import * as trailingSlash from "./trailingSlash.js";
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
||||
|
||||
/**
|
||||
* Return the value as an object. If the value is already an object it will be
|
||||
* returned as is. If the value is a primitive, it will be wrapped in an object:
|
||||
* a string will be wrapped in a String object, a number will be wrapped in a
|
||||
* Number object, and a boolean will be wrapped in a Boolean object.
|
||||
*
|
||||
* @param {any} value
|
||||
*/
|
||||
export function box(value) {
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
return new String(value);
|
||||
case "number":
|
||||
return new Number(value);
|
||||
case "boolean":
|
||||
return new Boolean(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array or plain object from the given keys and values.
|
||||
*
|
||||
* If the given plain object has only sequential integer keys, return the
|
||||
* values as an array. Otherwise, create a plain object with the keys and
|
||||
* values.
|
||||
*
|
||||
* @param {any[]} keys
|
||||
* @param {any[]} values
|
||||
*/
|
||||
export function castArrayLike(keys, values) {
|
||||
let isArrayLike = false;
|
||||
|
||||
// Need at least one key to count as an array
|
||||
if (keys.length > 0) {
|
||||
// Assume it's an array
|
||||
isArrayLike = true;
|
||||
// Then check if all the keys are sequential integers
|
||||
let expectedIndex = 0;
|
||||
for (const key of keys) {
|
||||
const index = Number(key);
|
||||
if (key === "" || isNaN(index) || index !== expectedIndex) {
|
||||
// Not array-like
|
||||
isArrayLike = false;
|
||||
break;
|
||||
}
|
||||
expectedIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return isArrayLike
|
||||
? values
|
||||
: Object.fromEntries(keys.map((key, i) => [key, values[i]]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Object prototype at the root of the object's prototype chain.
|
||||
*
|
||||
* This is used by functions like isPlainObject() to handle cases where the
|
||||
* `Object` at the root prototype chain is in a different realm.
|
||||
*
|
||||
* @param {any} object
|
||||
*/
|
||||
export function getRealmObjectPrototype(object) {
|
||||
if (Object.getPrototypeOf(object) === null) {
|
||||
// The object has no prototype.
|
||||
return null;
|
||||
}
|
||||
let proto = object;
|
||||
while (Object.getPrototypeOf(proto) !== null) {
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
// Names of OS-generated files that should not be enumerated
|
||||
export const hiddenFileNames = [".DS_Store"];
|
||||
|
||||
/**
|
||||
* Return true if the object is in a packed form (or can be readily packed into
|
||||
* a form) that can be given to fs.writeFile or response.write().
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is import("../index.ts").Packed}
|
||||
*/
|
||||
export function isPacked(obj) {
|
||||
return (
|
||||
typeof obj === "string" ||
|
||||
obj instanceof ArrayBuffer ||
|
||||
obj instanceof ReadableStream ||
|
||||
obj instanceof String ||
|
||||
obj instanceof TypedArray
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the object is a plain JavaScript object created by `{}`,
|
||||
* `new Object()`, or `Object.create(null)`.
|
||||
*
|
||||
* This function also considers object-like things with no prototype (like a
|
||||
* `Module`) as plain objects.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is import("../index.ts").PlainObject}
|
||||
*/
|
||||
export function isPlainObject(obj) {
|
||||
// From https://stackoverflow.com/q/51722354/76472
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We treat object-like things with no prototype (like a Module) as plain
|
||||
// objects.
|
||||
if (Object.getPrototypeOf(obj) === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do we inherit directly from Object in this realm?
|
||||
return Object.getPrototypeOf(obj) === getRealmObjectPrototype(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the value is a primitive JavaScript value.
|
||||
*
|
||||
* @param {any} value
|
||||
*/
|
||||
export function isPrimitive(value) {
|
||||
// Check for null first, since typeof null === "object".
|
||||
if (value === null) {
|
||||
return true;
|
||||
}
|
||||
const type = typeof value;
|
||||
return type !== "object" && type !== "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the object is a string or object with a non-trival `toString`
|
||||
* method.
|
||||
*
|
||||
* @param {any} obj
|
||||
* @returns {obj is import("../index.ts").StringLike}
|
||||
*/
|
||||
export function isStringLike(obj) {
|
||||
if (typeof obj === "string") {
|
||||
return true;
|
||||
} else if (obj?.toString === undefined) {
|
||||
return false;
|
||||
} else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
|
||||
// The stupid Object.prototype.toString implementation always returns
|
||||
// "[object Object]", so if that's the only toString method the object has,
|
||||
// we return false.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isUnpackable(obj) {
|
||||
return (
|
||||
isPacked(obj) && typeof (/** @type {any} */ (obj).unpack) === "function"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path like "/foo/bar/baz", return an array of keys like ["foo/",
|
||||
* "bar/", "baz"].
|
||||
*
|
||||
* Leading slashes are ignored. Consecutive slashes will be ignored. Trailing
|
||||
* slashes are preserved.
|
||||
*
|
||||
* @param {string} pathname
|
||||
*/
|
||||
export function keysFromPath(pathname) {
|
||||
// Split the path at each slash
|
||||
let keys = pathname.split("/");
|
||||
if (keys[0] === "") {
|
||||
// The path begins with a slash; drop that part.
|
||||
keys.shift();
|
||||
}
|
||||
if (keys.at(-1) === "") {
|
||||
// The path ends with a slash; drop that part.
|
||||
keys.pop();
|
||||
}
|
||||
// Drop any empty keys
|
||||
keys = keys.filter((key) => key !== "");
|
||||
// Add the trailing slash back to all keys but the last
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
keys[i] += "/";
|
||||
}
|
||||
// Add trailing slash to last key if path ended with a slash
|
||||
if (keys.length > 0 && trailingSlash.has(pathname)) {
|
||||
keys[keys.length - 1] += "/";
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two strings using [natural sort
|
||||
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
||||
*/
|
||||
export const naturalOrder = new Intl.Collator(undefined, {
|
||||
numeric: true,
|
||||
}).compare;
|
||||
|
||||
/**
|
||||
* Return a slash-separated path for the given keys.
|
||||
*
|
||||
* This takes care to avoid adding consecutive slashes if they keys themselves
|
||||
* already have trailing slashes.
|
||||
*
|
||||
* @param {string[]} keys
|
||||
*/
|
||||
export function pathFromKeys(keys) {
|
||||
const normalized = keys.map((key) => trailingSlash.remove(key));
|
||||
return normalized.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a series of functions to a value, passing the result of each function
|
||||
* to the next one.
|
||||
*
|
||||
* @param {any} start
|
||||
* @param {...Function} fns
|
||||
*/
|
||||
export async function pipeline(start, ...fns) {
|
||||
return fns.reduce(async (acc, fn) => fn(await acc), start);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the child object doesn't have a parent yet, set it to the indicated
|
||||
* parent. If the child is an AsyncTree, set the `parent` property. Otherwise,
|
||||
* set the `symbols.parent` property.
|
||||
*
|
||||
* @param {*} child
|
||||
* @param {*} parent
|
||||
*/
|
||||
export function setParent(child, parent) {
|
||||
if (Tree.isAsyncTree(child)) {
|
||||
// Value is a subtree; set its parent to this tree.
|
||||
if (!child.parent) {
|
||||
child.parent = parent;
|
||||
}
|
||||
} else if (Object.isExtensible(child) && !child[symbols.parent]) {
|
||||
// Add parent reference as a symbol to avoid polluting the object. This
|
||||
// reference will be used if the object is later used as a tree. We set
|
||||
// `enumerable` to false even thought this makes no practical difference
|
||||
// (symbols are never enumerated) because it can provide a hint in the
|
||||
// debugger that the property is for internal use.
|
||||
Object.defineProperty(child, symbols.parent, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
value: parent,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given input to the plainest possible JavaScript value. This
|
||||
* helper is intended for functions that want to accept an argument from the ori
|
||||
* CLI, which could a string, a stream of data, or some other kind of JavaScript
|
||||
* object.
|
||||
*
|
||||
* If the input is a function, it will be invoked and its result will be
|
||||
* processed.
|
||||
*
|
||||
* If the input is a promise, it will be resolved and its result will be
|
||||
* processed.
|
||||
*
|
||||
* If the input is treelike, it will be converted to a plain JavaScript object,
|
||||
* recursively traversing the tree and converting all values to plain types.
|
||||
*
|
||||
* If the input is stringlike, its text will be returned.
|
||||
*
|
||||
* If the input is a ArrayBuffer or typed array, it will be interpreted as UTF-8
|
||||
* text if it does not contain unprintable characters. If it does, it will be
|
||||
* returned as a base64-encoded string.
|
||||
*
|
||||
* If the input has a custom class instance, its public properties will be
|
||||
* returned as a plain object.
|
||||
*
|
||||
* @param {any} input
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function toPlainValue(input) {
|
||||
if (input instanceof Function) {
|
||||
// Invoke function
|
||||
input = input();
|
||||
}
|
||||
if (input instanceof Promise) {
|
||||
// Resolve promise
|
||||
input = await input;
|
||||
}
|
||||
|
||||
if (isPrimitive(input) || input instanceof Date) {
|
||||
return input;
|
||||
} else if (Tree.isTreelike(input)) {
|
||||
const mapped = await Tree.map(input, (value) => toPlainValue(value));
|
||||
return Tree.plain(mapped);
|
||||
} else if (isStringLike(input)) {
|
||||
return toString(input);
|
||||
} else if (input instanceof ArrayBuffer || input instanceof TypedArray) {
|
||||
// Try to interpret the buffer as UTF-8 text, otherwise use base64.
|
||||
const text = toString(input);
|
||||
if (text !== null) {
|
||||
return text;
|
||||
} else {
|
||||
return toBase64(input);
|
||||
}
|
||||
} else {
|
||||
// Some other kind of class instance; return its public properties.
|
||||
const plain = {};
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
plain[key] = await toPlainValue(value);
|
||||
}
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
|
||||
function toBase64(object) {
|
||||
if (typeof Buffer !== "undefined") {
|
||||
// Node.js environment
|
||||
return Buffer.from(object).toString("base64");
|
||||
} else {
|
||||
// Browser environment
|
||||
let binary = "";
|
||||
const bytes = new Uint8Array(object);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string form of the object, handling cases not generally handled by
|
||||
* the standard JavaScript `toString()` method:
|
||||
*
|
||||
* 1. If the object is an ArrayBuffer or TypedArray, decode the array as UTF-8.
|
||||
* 2. If the object is otherwise a plain JavaScript object with the useless
|
||||
* default toString() method, return null instead of "[object Object]". In
|
||||
* practice, it's generally more useful to have this method fail than to
|
||||
* return a useless string.
|
||||
* 3. If the object is a defined primitive value, return the result of
|
||||
* String(object).
|
||||
*
|
||||
* Otherwise return null.
|
||||
*
|
||||
* @param {any} object
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export function toString(object) {
|
||||
if (object instanceof ArrayBuffer || object instanceof TypedArray) {
|
||||
// Treat the buffer as UTF-8 text.
|
||||
const decoded = textDecoder.decode(object);
|
||||
// If the result appears to contain non-printable characters, it's probably not a string.
|
||||
// https://stackoverflow.com/a/1677660/76472
|
||||
const hasNonPrintableCharacters = /[\x00-\x08\x0E-\x1F]/.test(decoded);
|
||||
return hasNonPrintableCharacters ? null : decoded;
|
||||
} else if (isStringLike(object) || (object !== null && isPrimitive(object))) {
|
||||
return String(object);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
386
node_modules/@weborigami/async-tree/test/Tree.test.js
generated
vendored
Normal file
386
node_modules/@weborigami/async-tree/test/Tree.test.js
generated
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import MapTree from "../src/drivers/MapTree.js";
|
||||
import { DeepObjectTree, ObjectTree, Tree } from "../src/internal.js";
|
||||
import * as symbols from "../src/symbols.js";
|
||||
|
||||
describe("Tree", () => {
|
||||
test("assign applies one tree to another", async () => {
|
||||
const target = new DeepObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
more: {
|
||||
d: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const source = new DeepObjectTree({
|
||||
a: 4, // Overwrite existing value
|
||||
b: undefined, // Delete
|
||||
c: 5, // Add
|
||||
more: {
|
||||
// Should leave existing `more` keys alone.
|
||||
e: 6, // Add
|
||||
},
|
||||
// Add new subtree
|
||||
extra: {
|
||||
f: 7,
|
||||
},
|
||||
});
|
||||
|
||||
// Apply changes.
|
||||
const result = await Tree.assign(target, source);
|
||||
|
||||
assert.equal(result, target);
|
||||
const plain = await Tree.plain(target);
|
||||
assert.deepEqual(plain, {
|
||||
a: 4,
|
||||
c: 5,
|
||||
more: {
|
||||
d: 3,
|
||||
e: 6,
|
||||
},
|
||||
extra: {
|
||||
f: 7,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("assign() can apply updates to an array", async () => {
|
||||
const target = new ObjectTree(["a", "b", "c"]);
|
||||
await Tree.assign(target, ["d", "e"]);
|
||||
assert.deepEqual(await Tree.plain(target), ["d", "e", "c"]);
|
||||
});
|
||||
|
||||
test("clear() removes all values", async () => {
|
||||
const fixture = createFixture();
|
||||
await Tree.clear(fixture);
|
||||
assert.deepEqual(Array.from(await Tree.entries(fixture)), []);
|
||||
});
|
||||
|
||||
test("entries() returns the [key, value] pairs", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.deepEqual(Array.from(await Tree.entries(fixture)), [
|
||||
["Alice.md", "Hello, **Alice**."],
|
||||
["Bob.md", "Hello, **Bob**."],
|
||||
["Carol.md", "Hello, **Carol**."],
|
||||
]);
|
||||
});
|
||||
|
||||
test("forEach() invokes a callback for each entry", async () => {
|
||||
const fixture = createFixture();
|
||||
const results = {};
|
||||
await Tree.forEach(fixture, async (value, key) => {
|
||||
results[key] = value;
|
||||
});
|
||||
assert.deepEqual(results, {
|
||||
"Alice.md": "Hello, **Alice**.",
|
||||
"Bob.md": "Hello, **Bob**.",
|
||||
"Carol.md": "Hello, **Carol**.",
|
||||
});
|
||||
});
|
||||
|
||||
test("from() returns an async tree as is", async () => {
|
||||
const tree1 = new ObjectTree({
|
||||
a: "Hello, a.",
|
||||
});
|
||||
const tree2 = Tree.from(tree1);
|
||||
assert.equal(tree2, tree1);
|
||||
});
|
||||
|
||||
test("from() uses an object's unpack() method if defined", async () => {
|
||||
const obj = new String();
|
||||
/** @type {any} */ (obj).unpack = () => ({
|
||||
a: "Hello, a.",
|
||||
});
|
||||
const tree = Tree.from(obj);
|
||||
assert.deepEqual(await Tree.plain(tree), {
|
||||
a: "Hello, a.",
|
||||
});
|
||||
});
|
||||
|
||||
test("from returns a deep object tree if deep option is true", async () => {
|
||||
const obj = {
|
||||
sub: {
|
||||
a: 1,
|
||||
},
|
||||
};
|
||||
const tree = Tree.from(obj, { deep: true });
|
||||
assert(tree instanceof DeepObjectTree);
|
||||
});
|
||||
|
||||
test("from returns a deep object tree if object has [deep] symbol set", async () => {
|
||||
const obj = {
|
||||
sub: {
|
||||
a: 1,
|
||||
},
|
||||
};
|
||||
Object.defineProperty(obj, symbols.deep, { value: true });
|
||||
const tree = Tree.from(obj);
|
||||
assert(tree instanceof DeepObjectTree);
|
||||
});
|
||||
|
||||
test("from() creates a deferred tree if unpack() returns a promise", async () => {
|
||||
const obj = new String();
|
||||
/** @type {any} */ (obj).unpack = async () => ({
|
||||
a: "Hello, a.",
|
||||
});
|
||||
const tree = Tree.from(obj);
|
||||
assert.deepEqual(await Tree.plain(tree), {
|
||||
a: "Hello, a.",
|
||||
});
|
||||
});
|
||||
|
||||
test("from() autoboxes primitive values", async () => {
|
||||
const tree = Tree.from("Hello, world.");
|
||||
const slice = await tree.get("slice");
|
||||
const result = await slice(0, 5);
|
||||
assert.equal(result, "Hello");
|
||||
});
|
||||
|
||||
test("has returns true if the key exists", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.equal(await Tree.has(fixture, "Alice.md"), true);
|
||||
assert.equal(await Tree.has(fixture, "David.md"), false);
|
||||
});
|
||||
|
||||
test("isAsyncTree returns true if the object is a tree", () => {
|
||||
const missingGetAndKeys = {};
|
||||
assert(!Tree.isAsyncTree(missingGetAndKeys));
|
||||
|
||||
const missingIterator = {
|
||||
async get() {},
|
||||
};
|
||||
assert(!Tree.isAsyncTree(missingIterator));
|
||||
|
||||
const missingGet = {
|
||||
async keys() {},
|
||||
};
|
||||
assert(!Tree.isAsyncTree(missingGet));
|
||||
|
||||
const hasGetAndKeys = {
|
||||
async get() {},
|
||||
async keys() {},
|
||||
};
|
||||
assert(Tree.isAsyncTree(hasGetAndKeys));
|
||||
});
|
||||
|
||||
test("isAsyncMutableTree returns true if the object is a mutable tree", () => {
|
||||
assert.equal(
|
||||
Tree.isAsyncMutableTree({
|
||||
get() {},
|
||||
keys() {},
|
||||
}),
|
||||
false
|
||||
);
|
||||
assert.equal(Tree.isAsyncMutableTree(createFixture()), true);
|
||||
});
|
||||
|
||||
test("isTreelike() returns true if the argument can be cast to an async tree", () => {
|
||||
assert(!Tree.isTreelike(null));
|
||||
assert(Tree.isTreelike({}));
|
||||
assert(Tree.isTreelike([]));
|
||||
assert(Tree.isTreelike(new Map()));
|
||||
assert(Tree.isTreelike(new Set()));
|
||||
});
|
||||
|
||||
test("map() maps values", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: "Alice",
|
||||
more: {
|
||||
b: "Bob",
|
||||
},
|
||||
});
|
||||
const mapped = Tree.map(tree, {
|
||||
deep: true,
|
||||
value: (value) => value.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(mapped), {
|
||||
a: "ALICE",
|
||||
more: {
|
||||
b: "BOB",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("mapReduce() can map values and reduce them", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
more: {
|
||||
c: 3,
|
||||
},
|
||||
d: 4,
|
||||
});
|
||||
const reduced = await Tree.mapReduce(
|
||||
tree,
|
||||
(value) => value,
|
||||
async (values) => String.prototype.concat(...values)
|
||||
);
|
||||
assert.deepEqual(reduced, "1234");
|
||||
});
|
||||
|
||||
test("paths returns an array of paths to the values in the tree", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: {
|
||||
d: 3,
|
||||
e: 4,
|
||||
},
|
||||
});
|
||||
assert.deepEqual(await Tree.paths(tree), ["a", "b", "c/d", "c/e"]);
|
||||
});
|
||||
|
||||
test("plain() produces a plain object version of a tree", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: 1,
|
||||
// Slashes should be normalized
|
||||
"sub1/": {
|
||||
b: 2,
|
||||
},
|
||||
sub2: {
|
||||
c: 3,
|
||||
},
|
||||
});
|
||||
const plain = await Tree.plain(tree);
|
||||
assert.deepEqual(plain, {
|
||||
a: 1,
|
||||
sub1: {
|
||||
b: 2,
|
||||
},
|
||||
sub2: {
|
||||
c: 3,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("plain() produces an array for an array-like tree", async () => {
|
||||
const original = ["a", "b", "c"];
|
||||
const tree = new ObjectTree(original);
|
||||
const plain = await Tree.plain(tree);
|
||||
assert.deepEqual(plain, original);
|
||||
});
|
||||
|
||||
test("plain() leaves an array-like tree as an object if keys aren't consecutive", async () => {
|
||||
const original = {
|
||||
0: "a",
|
||||
1: "b",
|
||||
// missing
|
||||
3: "c",
|
||||
};
|
||||
const tree = new ObjectTree(original);
|
||||
const plain = await Tree.plain(tree);
|
||||
assert.deepEqual(plain, original);
|
||||
});
|
||||
|
||||
test("plain() returns empty array or object for ObjectTree as necessary", async () => {
|
||||
const tree = new ObjectTree({});
|
||||
assert.deepEqual(await Tree.plain(tree), {});
|
||||
const arrayTree = new ObjectTree([]);
|
||||
assert.deepEqual(await Tree.plain(arrayTree), []);
|
||||
});
|
||||
|
||||
test("plain() awaits async properties", async () => {
|
||||
const object = {
|
||||
get name() {
|
||||
return Promise.resolve("Alice");
|
||||
},
|
||||
};
|
||||
assert.deepEqual(await Tree.plain(object), { name: "Alice" });
|
||||
});
|
||||
|
||||
test("plain() coerces TypedArray values to strings", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: new TextEncoder().encode("Hello, world."),
|
||||
});
|
||||
const plain = await Tree.plain(tree);
|
||||
assert.equal(plain.a, "Hello, world.");
|
||||
});
|
||||
|
||||
test("remove method removes a value", async () => {
|
||||
const fixture = createFixture();
|
||||
await Tree.remove(fixture, "Alice.md");
|
||||
assert.deepEqual(Array.from(await Tree.entries(fixture)), [
|
||||
["Bob.md", "Hello, **Bob**."],
|
||||
["Carol.md", "Hello, **Carol**."],
|
||||
]);
|
||||
});
|
||||
|
||||
test("toFunction returns a function that invokes a tree's get() method", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
});
|
||||
const fn = Tree.toFunction(tree);
|
||||
assert.equal(await fn("a"), 1);
|
||||
assert.equal(await fn("b"), 2);
|
||||
});
|
||||
|
||||
test("traverse() a path of keys", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a1: 1,
|
||||
a2: {
|
||||
b1: 2,
|
||||
b2: {
|
||||
c1: 3,
|
||||
c2: 4,
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(await Tree.traverse(tree), tree);
|
||||
assert.equal(await Tree.traverse(tree, "a1"), 1);
|
||||
assert.equal(await Tree.traverse(tree, "a2", "b2", "c2"), 4);
|
||||
assert.equal(
|
||||
await Tree.traverse(tree, "a2", "doesntexist", "c2"),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("traverse() a function with fixed number of arguments", async () => {
|
||||
const tree = (a, b) => ({
|
||||
c: "Result",
|
||||
});
|
||||
assert.equal(await Tree.traverse(tree, "a", "b", "c"), "Result");
|
||||
});
|
||||
|
||||
test("traverse() from one tree into another", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: {
|
||||
b: new MapTree([
|
||||
["c", "Hello"],
|
||||
["d", "Goodbye"],
|
||||
]),
|
||||
},
|
||||
});
|
||||
assert.equal(await Tree.traverse(tree, "a", "b", "c"), "Hello");
|
||||
});
|
||||
|
||||
test("traversePath() traverses a slash-separated path", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: {
|
||||
b: {
|
||||
c: "Hello",
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(await Tree.traversePath(tree, "a/b/c"), "Hello");
|
||||
});
|
||||
|
||||
test("values() returns the store's values", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.deepEqual(Array.from(await Tree.values(fixture)), [
|
||||
"Hello, **Alice**.",
|
||||
"Hello, **Bob**.",
|
||||
"Hello, **Carol**.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture() {
|
||||
return new ObjectTree({
|
||||
"Alice.md": "Hello, **Alice**.",
|
||||
"Bob.md": "Hello, **Bob**.",
|
||||
"Carol.md": "Hello, **Carol**.",
|
||||
});
|
||||
}
|
||||
54
node_modules/@weborigami/async-tree/test/browser/assert.js
generated
vendored
Normal file
54
node_modules/@weborigami/async-tree/test/browser/assert.js
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* A simple test runner for the browser to run the subset of the Node.s test
|
||||
* runner used by the project.
|
||||
*/
|
||||
|
||||
export default function assert(condition) {
|
||||
if (!condition) {
|
||||
throw new Error("Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal = (actual, expected) => {
|
||||
if (Number.isNaN(actual) && Number.isNaN(expected)) {
|
||||
return;
|
||||
} else if (actual == expected) {
|
||||
return;
|
||||
} else {
|
||||
throw new Error(`Expected ${expected} but got ${actual}`);
|
||||
}
|
||||
};
|
||||
|
||||
// This is a simplified deepEqual test that examines the conditions we care
|
||||
// about. For reference, the actual Node assert.deepEqual is much more complex:
|
||||
// see https://github.com/nodejs/node/blob/main/lib/internal/util/comparisons.js
|
||||
assert.deepEqual = (actual, expected) => {
|
||||
if (actual === expected) {
|
||||
return;
|
||||
} else if (
|
||||
typeof actual === "object" &&
|
||||
actual != null &&
|
||||
typeof expected === "object" &&
|
||||
expected != null &&
|
||||
Object.keys(actual).length === Object.keys(expected).length
|
||||
) {
|
||||
for (const prop in actual) {
|
||||
if (!expected.hasOwnProperty(prop)) {
|
||||
break;
|
||||
}
|
||||
assert.deepEqual(actual[prop], expected[prop]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Expected ${expected} but got ${actual}`);
|
||||
};
|
||||
|
||||
assert.rejects = async (promise) => {
|
||||
try {
|
||||
await promise;
|
||||
throw new Error("Expected promise to reject but it resolved");
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
35
node_modules/@weborigami/async-tree/test/browser/index.html
generated
vendored
Normal file
35
node_modules/@weborigami/async-tree/test/browser/index.html
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"node:assert": "./assert.js",
|
||||
"node:test": "./testRunner.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- Omit FileTree.test.js, which is Node.js only -->
|
||||
<!-- Omit SiteTree.test.js, which requires mocks -->
|
||||
<script type="module" src="../BrowserFileTree.test.js"></script>
|
||||
<script type="module" src="../DeferredTree.test.js"></script>
|
||||
<script type="module" src="../FunctionTree.test.js"></script>
|
||||
<script type="module" src="../MapTree.test.js"></script>
|
||||
<script type="module" src="../ObjectTree.test.js"></script>
|
||||
<script type="module" src="../SetTree.test.js"></script>
|
||||
<script type="module" src="../Tree.test.js"></script>
|
||||
<script type="module" src="../operations/cache.test.js"></script>
|
||||
<script type="module" src="../operations/merge.test.js"></script>
|
||||
<script type="module" src="../operations/deepMerge.test.js"></script>
|
||||
<script type="module" src="../transforms/cachedKeyMaps.test.js"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="../transforms/keyFunctionsForExtensions.test.js"
|
||||
></script>
|
||||
<script type="module" src="../transforms/mapFn.test.js"></script>
|
||||
<script type="module" src="../utilities.test.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
51
node_modules/@weborigami/async-tree/test/browser/testRunner.js
generated
vendored
Normal file
51
node_modules/@weborigami/async-tree/test/browser/testRunner.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* A simple test runner for the browser to run the subset of the Node.s test
|
||||
* runner used by the project.
|
||||
*/
|
||||
|
||||
let promises = {};
|
||||
let currentSuite;
|
||||
|
||||
const markers = {
|
||||
success: "✅",
|
||||
skipped: "ー",
|
||||
fail: "❌",
|
||||
};
|
||||
|
||||
export async function describe(name, fn) {
|
||||
promises[name] = [];
|
||||
currentSuite = name;
|
||||
await fn();
|
||||
const results = await Promise.all(promises[name]);
|
||||
const someFailed = results.some((result) => result.result === "fail");
|
||||
const header = `${someFailed ? markers.fail : markers.success} ${name}`;
|
||||
console[someFailed ? "group" : "groupCollapsed"](header);
|
||||
for (const result of results) {
|
||||
const marker = markers[result.result];
|
||||
const name = result.name;
|
||||
const message = result.result === "fail" ? `: ${result.message}` : "";
|
||||
const skipped = result.result === "skipped" ? " [skipped]" : "";
|
||||
console.log(`${marker} ${name}${message}${skipped}`);
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Node test() calls can call an async function, but the test() function isn't
|
||||
// declared async. We implicitly wrap the test call with a Promise and add it to
|
||||
// the list of promises for the current suite.
|
||||
export async function test(name, fn) {
|
||||
promises[currentSuite].push(runTest(name, fn));
|
||||
}
|
||||
|
||||
test.skip = (name, fn) => {
|
||||
promises[currentSuite].push(Promise.resolve({ result: "skipped", name }));
|
||||
};
|
||||
|
||||
async function runTest(name, fn) {
|
||||
try {
|
||||
await fn();
|
||||
return { result: "success", name };
|
||||
} catch (/** @type {any} */ error) {
|
||||
return { result: "fail", name, message: error.message };
|
||||
}
|
||||
}
|
||||
153
node_modules/@weborigami/async-tree/test/drivers/BrowserFileTree.test.js
generated
vendored
Normal file
153
node_modules/@weborigami/async-tree/test/drivers/BrowserFileTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import BrowserFileTree from "../../src/drivers/BrowserFileTree.js";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
|
||||
// Skip these tests if we're not in a browser.
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
if (isBrowser) {
|
||||
describe("BrowserFileTree", async () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const fixture = await createFixture();
|
||||
assert.deepEqual(Array.from(await fixture.keys()), [
|
||||
"Alice.md",
|
||||
"Bob.md",
|
||||
"Carol.md",
|
||||
"subfolder/",
|
||||
]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const fixture = await createFixture();
|
||||
const buffer = await fixture.get("Alice.md");
|
||||
assert.equal(text(buffer), "Hello, **Alice**.");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = await createFixture();
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
|
||||
test("getting empty key returns undefined", async () => {
|
||||
const fixture = await createFixture();
|
||||
assert.equal(await fixture.get(""), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const fixture = await createFixture();
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const fixture = await createFixture();
|
||||
const subfolder = await fixture.get("subfolder");
|
||||
assert.equal(subfolder.parent, fixture);
|
||||
});
|
||||
|
||||
test("can retrieve values with optional trailing slash", async () => {
|
||||
const fixture = await createFixture();
|
||||
assert(await fixture.get("Alice.md"));
|
||||
assert(await fixture.get("Alice.md/"));
|
||||
assert(await fixture.get("subfolder"));
|
||||
assert(await fixture.get("subfolder/"));
|
||||
});
|
||||
|
||||
test("can set a value", async () => {
|
||||
const fixture = await createFixture();
|
||||
|
||||
// Update existing key.
|
||||
await fixture.set("Alice.md", "Goodbye, **Alice**.");
|
||||
|
||||
// New key.
|
||||
await fixture.set("David.md", "Hello, **David**.");
|
||||
|
||||
// Delete key.
|
||||
await fixture.set("Bob.md", undefined);
|
||||
|
||||
// Delete non-existent key.
|
||||
await fixture.set("xyz", undefined);
|
||||
|
||||
assert.deepEqual(await strings(fixture), {
|
||||
"Alice.md": "Goodbye, **Alice**.",
|
||||
"Carol.md": "Hello, **Carol**.",
|
||||
"David.md": "Hello, **David**.",
|
||||
subfolder: {},
|
||||
});
|
||||
});
|
||||
|
||||
test("can create a subfolder via set", async () => {
|
||||
const fixture = await createFixture();
|
||||
const tree = {
|
||||
async get(key) {
|
||||
const name = key.replace(/\.md$/, "");
|
||||
return `Hello, **${name}**.`;
|
||||
},
|
||||
async keys() {
|
||||
return ["Ellen.md"];
|
||||
},
|
||||
};
|
||||
await fixture.set("more", tree);
|
||||
assert.deepEqual(await strings(fixture), {
|
||||
"Alice.md": "Hello, **Alice**.",
|
||||
"Bob.md": "Hello, **Bob**.",
|
||||
"Carol.md": "Hello, **Carol**.",
|
||||
more: {
|
||||
"Ellen.md": "Hello, **Ellen**.",
|
||||
},
|
||||
subfolder: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function createFile(directory, name, contents) {
|
||||
const file = await directory.getFileHandle(name, { create: true });
|
||||
const writable = await file.createWritable();
|
||||
await writable.write(contents);
|
||||
await writable.close();
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
async function createFixture() {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const directory = await root.getDirectoryHandle("async-tree", {
|
||||
create: true,
|
||||
});
|
||||
|
||||
// Create a new subdirectory for each test.
|
||||
const subdirectoryName = `test${count++}`;
|
||||
|
||||
// Delete any pre-existing subdirectory with that name.
|
||||
try {
|
||||
await directory.removeEntry(subdirectoryName, { recursive: true });
|
||||
} catch (e) {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
||||
const subdirectory = await directory.getDirectoryHandle(subdirectoryName, {
|
||||
create: true,
|
||||
});
|
||||
|
||||
await createFile(subdirectory, "Alice.md", "Hello, **Alice**.");
|
||||
await createFile(subdirectory, "Bob.md", "Hello, **Bob**.");
|
||||
await createFile(subdirectory, "Carol.md", "Hello, **Carol**.");
|
||||
|
||||
await subdirectory.getDirectoryHandle("subfolder", {
|
||||
create: true,
|
||||
});
|
||||
|
||||
return new BrowserFileTree(subdirectory);
|
||||
}
|
||||
|
||||
async function strings(tree) {
|
||||
return Tree.plain(Tree.map(tree, (value) => text(value)));
|
||||
}
|
||||
|
||||
function text(arrayBuffer) {
|
||||
return new TextDecoder().decode(arrayBuffer);
|
||||
}
|
||||
17
node_modules/@weborigami/async-tree/test/drivers/DeepMapTree.test.js
generated
vendored
Normal file
17
node_modules/@weborigami/async-tree/test/drivers/DeepMapTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import DeepMapTree from "../../src/drivers/DeepMapTree.js";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
|
||||
describe("DeepMapTree", () => {
|
||||
test("returns a DeepMapTree for value that's a Map", async () => {
|
||||
const tree = new DeepMapTree([
|
||||
["a", 1],
|
||||
["map", new Map([["b", 2]])],
|
||||
]);
|
||||
const map = await tree.get("map");
|
||||
assert.equal(map instanceof DeepMapTree, true);
|
||||
assert.deepEqual(await Tree.plain(map), { b: 2 });
|
||||
assert.equal(map.parent, tree);
|
||||
});
|
||||
});
|
||||
35
node_modules/@weborigami/async-tree/test/drivers/DeepObjectTree.test.js
generated
vendored
Normal file
35
node_modules/@weborigami/async-tree/test/drivers/DeepObjectTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, Tree } from "../../src/internal.js";
|
||||
|
||||
describe("DeepObjectTree", () => {
|
||||
test("returns an ObjectTree for value that's a plain sub-object or sub-array", async () => {
|
||||
const tree = createFixture();
|
||||
|
||||
const object = await tree.get("object");
|
||||
assert.equal(object instanceof DeepObjectTree, true);
|
||||
assert.deepEqual(await Tree.plain(object), { b: 2 });
|
||||
assert.equal(object.parent, tree);
|
||||
|
||||
const array = await tree.get("array");
|
||||
assert.equal(array instanceof DeepObjectTree, true);
|
||||
assert.deepEqual(await Tree.plain(array), [3]);
|
||||
assert.equal(array.parent, tree);
|
||||
});
|
||||
|
||||
test("adds trailing slashes to keys for subtrees including plain objects or arrays", async () => {
|
||||
const tree = createFixture();
|
||||
const keys = Array.from(await tree.keys());
|
||||
assert.deepEqual(keys, ["a", "object/", "array/"]);
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture() {
|
||||
return new DeepObjectTree({
|
||||
a: 1,
|
||||
object: {
|
||||
b: 2,
|
||||
},
|
||||
array: [3],
|
||||
});
|
||||
}
|
||||
22
node_modules/@weborigami/async-tree/test/drivers/DeferredTree.test.js
generated
vendored
Normal file
22
node_modules/@weborigami/async-tree/test/drivers/DeferredTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import DeferredTree from "../../src/drivers/DeferredTree.js";
|
||||
import { ObjectTree, Tree } from "../../src/internal.js";
|
||||
|
||||
describe("DeferredTree", () => {
|
||||
test("lazy-loads a treelike object", async () => {
|
||||
const tree = new DeferredTree(async () => ({ a: 1, b: 2, c: 3 }));
|
||||
assert.deepEqual(await Tree.plain(tree), { a: 1, b: 2, c: 3 });
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const object = {
|
||||
a: 1,
|
||||
};
|
||||
const parent = new ObjectTree({});
|
||||
const fixture = new DeferredTree(() => object);
|
||||
fixture.parent = parent;
|
||||
const tree = await fixture.tree();
|
||||
assert.equal(tree.parent, parent);
|
||||
});
|
||||
});
|
||||
116
node_modules/@weborigami/async-tree/test/drivers/ExplorableSiteTree.test.js
generated
vendored
Normal file
116
node_modules/@weborigami/async-tree/test/drivers/ExplorableSiteTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
import assert from "node:assert";
|
||||
import { beforeEach, describe, mock, test } from "node:test";
|
||||
import ExplorableSiteTree from "../../src/drivers/ExplorableSiteTree.js";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
const mockHost = "https://mock";
|
||||
|
||||
const mockResponses = {
|
||||
"/.keys.json": {
|
||||
data: JSON.stringify(["about/", "index.html"]),
|
||||
},
|
||||
"/about": {
|
||||
redirected: true,
|
||||
status: 301,
|
||||
url: "https://mock/about/",
|
||||
},
|
||||
"/about/.keys.json": {
|
||||
data: JSON.stringify(["Alice.html", "Bob.html", "Carol.html"]),
|
||||
},
|
||||
"/about/Alice.html": {
|
||||
data: "Hello, Alice!",
|
||||
},
|
||||
"/about/Bob.html": {
|
||||
data: "Hello, Bob!",
|
||||
},
|
||||
"/about/Carol.html": {
|
||||
data: "Hello, Carol!",
|
||||
},
|
||||
"/index.html": {
|
||||
data: "Home page",
|
||||
},
|
||||
};
|
||||
|
||||
describe("ExplorableSiteTree", () => {
|
||||
beforeEach(() => {
|
||||
mock.method(global, "fetch", mockFetch);
|
||||
});
|
||||
|
||||
test("can get the keys of a tree", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
const keys = await fixture.keys();
|
||||
assert.deepEqual(Array.from(keys), ["about/", "index.html"]);
|
||||
});
|
||||
|
||||
test("can get a plain value for a key", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
const arrayBuffer = await fixture.get("index.html");
|
||||
const text = textDecoder.decode(arrayBuffer);
|
||||
assert.equal(text, "Home page");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test("can return a new tree for a key that redirects", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
const about = await fixture.get("about");
|
||||
assert(about instanceof ExplorableSiteTree);
|
||||
assert.equal(about.href, "https://mock/about/");
|
||||
});
|
||||
|
||||
test("can convert a site to a plain object", async () => {
|
||||
const fixture = new ExplorableSiteTree(mockHost);
|
||||
// Convert buffers to strings.
|
||||
const strings = Tree.map(fixture, {
|
||||
deep: true,
|
||||
value: (value) => textDecoder.decode(value),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(strings), {
|
||||
about: {
|
||||
"Alice.html": "Hello, Alice!",
|
||||
"Bob.html": "Hello, Bob!",
|
||||
"Carol.html": "Hello, Carol!",
|
||||
},
|
||||
"index.html": "Home page",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function mockFetch(href) {
|
||||
if (!href.startsWith(mockHost)) {
|
||||
return { status: 404 };
|
||||
}
|
||||
const path = href.slice(mockHost.length);
|
||||
const mockedResponse = mockResponses[path];
|
||||
if (mockedResponse) {
|
||||
return Object.assign(
|
||||
{
|
||||
arrayBuffer: () => textEncoder.encode(mockedResponse.data).buffer,
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: () => mockedResponse.data,
|
||||
},
|
||||
mockedResponse
|
||||
);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
};
|
||||
}
|
||||
192
node_modules/@weborigami/async-tree/test/drivers/FileTree.test.js
generated
vendored
Normal file
192
node_modules/@weborigami/async-tree/test/drivers/FileTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
import assert from "node:assert";
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, test } from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import FileTree from "../../src/drivers/FileTree.js";
|
||||
import { ObjectTree, Tree } from "../../src/internal.js";
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const tempDirectory = path.join(dirname, "fixtures/temp");
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
|
||||
describe("FileTree", async () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
assert.deepEqual(Array.from(await fixture.keys()), [
|
||||
"Alice.md",
|
||||
"Bob.md",
|
||||
"Carol.md",
|
||||
"subfolder/",
|
||||
]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
const buffer = await fixture.get("Alice.md");
|
||||
const text = textDecoder.decode(buffer);
|
||||
assert.equal(text, "Hello, **Alice**.");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
|
||||
test("getting empty key returns undefined", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
assert.equal(await fixture.get(""), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test("can retrieve values with optional trailing slash", async () => {
|
||||
const fixture = createFixture("fixtures/markdown");
|
||||
assert(await fixture.get("Alice.md"));
|
||||
assert(await fixture.get("Alice.md/"));
|
||||
assert(await fixture.get("subfolder"));
|
||||
assert(await fixture.get("subfolder/"));
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const fixture = createFixture("fixtures");
|
||||
const markdown = await fixture.get("markdown");
|
||||
assert.equal(markdown.parent, fixture);
|
||||
});
|
||||
|
||||
test("can write out a file via set()", async () => {
|
||||
await createTempDirectory();
|
||||
|
||||
// Write out a file.
|
||||
const fileName = "file1";
|
||||
const fileText = "This is the first file.";
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set(fileName, fileText);
|
||||
|
||||
// Read it back in.
|
||||
const filePath = path.join(tempDirectory, fileName);
|
||||
const actualText = String(await fs.readFile(filePath));
|
||||
|
||||
assert.equal(fileText, actualText);
|
||||
|
||||
await removeTempDirectory();
|
||||
});
|
||||
|
||||
test("create subfolder via set() with empty object value", async () => {
|
||||
await createTempDirectory();
|
||||
|
||||
// Write out new, empty folder called "empty".
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set("empty", {});
|
||||
|
||||
// Verify folder exists and has no contents.
|
||||
const folderPath = path.join(tempDirectory, "empty");
|
||||
const stats = await fs.stat(folderPath);
|
||||
assert(stats.isDirectory());
|
||||
const files = await fs.readdir(folderPath);
|
||||
assert.deepEqual(files, []);
|
||||
|
||||
await removeTempDirectory();
|
||||
});
|
||||
|
||||
test("create subfolder via set() with empty tree value", async () => {
|
||||
await createTempDirectory();
|
||||
|
||||
// Write out new, empty folder called "empty".
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set("empty", new ObjectTree({}));
|
||||
|
||||
// Verify folder exists and has no contents.
|
||||
const folderPath = path.join(tempDirectory, "empty");
|
||||
const stats = await fs.stat(folderPath);
|
||||
assert(stats.isDirectory());
|
||||
const files = await fs.readdir(folderPath);
|
||||
assert.deepEqual(files, []);
|
||||
|
||||
await removeTempDirectory();
|
||||
});
|
||||
|
||||
test("can write out subfolder via set()", async () => {
|
||||
await createTempDirectory();
|
||||
|
||||
// Create a tiny set of "files".
|
||||
const obj = {
|
||||
file1: "This is the first file.",
|
||||
subfolder: {
|
||||
file2: "This is the second file.",
|
||||
},
|
||||
};
|
||||
|
||||
// Write out files as a new folder called "folder".
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set("folder", obj);
|
||||
|
||||
// Read them back in.
|
||||
const actualFiles = await tempFiles.get("folder");
|
||||
const strings = Tree.map(actualFiles, {
|
||||
deep: true,
|
||||
value: (buffer) => textDecoder.decode(buffer),
|
||||
});
|
||||
const plain = await Tree.plain(strings);
|
||||
assert.deepEqual(plain, obj);
|
||||
|
||||
await removeTempDirectory();
|
||||
});
|
||||
|
||||
test("can delete a file via set()", async () => {
|
||||
await createTempDirectory();
|
||||
const tempFile = path.join(tempDirectory, "file");
|
||||
await fs.writeFile(tempFile, "");
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set("file", undefined);
|
||||
let stats;
|
||||
try {
|
||||
stats = await fs.stat(tempFile);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
assert(stats === undefined);
|
||||
await removeTempDirectory();
|
||||
});
|
||||
|
||||
test("can delete a folder via set()", async () => {
|
||||
await createTempDirectory();
|
||||
const folder = path.join(tempDirectory, "folder");
|
||||
await fs.mkdir(folder);
|
||||
const tempFiles = new FileTree(tempDirectory);
|
||||
await tempFiles.set("folder", undefined);
|
||||
let stats;
|
||||
try {
|
||||
stats = await fs.stat(folder);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
assert(stats === undefined);
|
||||
await removeTempDirectory();
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture(fixturePath) {
|
||||
return new FileTree(path.join(dirname, fixturePath));
|
||||
}
|
||||
|
||||
async function createTempDirectory() {
|
||||
await fs.mkdir(tempDirectory, { recursive: true });
|
||||
}
|
||||
|
||||
async function removeTempDirectory() {
|
||||
await fs.rm(tempDirectory, { recursive: true });
|
||||
}
|
||||
46
node_modules/@weborigami/async-tree/test/drivers/FunctionTree.test.js
generated
vendored
Normal file
46
node_modules/@weborigami/async-tree/test/drivers/FunctionTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import FunctionTree from "../../src/drivers/FunctionTree.js";
|
||||
|
||||
describe("FunctionTree", async () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.deepEqual(Array.from(await fixture.keys()), [
|
||||
"Alice.md",
|
||||
"Bob.md",
|
||||
"Carol.md",
|
||||
]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const fixture = createFixture();
|
||||
const alice = await fixture.get("Alice.md");
|
||||
assert.equal(alice, "Hello, **Alice**.");
|
||||
});
|
||||
|
||||
test("getting a value from function with multiple arguments curries the function", async () => {
|
||||
const fixture = new FunctionTree((a, b, c) => a + b + c);
|
||||
const fnA = await fixture.get(1);
|
||||
const fnAB = await fnA.get(2);
|
||||
const result = await fnAB.get(3);
|
||||
assert.equal(result, 6);
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture() {
|
||||
return new FunctionTree(
|
||||
(key) => {
|
||||
if (key?.endsWith?.(".md")) {
|
||||
const name = key.slice(0, -3);
|
||||
return `Hello, **${name}**.`;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
["Alice.md", "Bob.md", "Carol.md"]
|
||||
);
|
||||
}
|
||||
59
node_modules/@weborigami/async-tree/test/drivers/MapTree.test.js
generated
vendored
Normal file
59
node_modules/@weborigami/async-tree/test/drivers/MapTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import MapTree from "../../src/drivers/MapTree.js";
|
||||
import * as symbols from "../../src/symbols.js";
|
||||
|
||||
describe("MapTree", () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.deepEqual(Array.from(await fixture.keys()), ["a", "b", "c"]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const fixture = createFixture();
|
||||
const a = await fixture.get("a");
|
||||
assert.equal(a, 1);
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const map = new Map([["more", new Map([["a", 1]])]]);
|
||||
const fixture = new MapTree(map);
|
||||
const more = await fixture.get("more");
|
||||
assert.equal(more[symbols.parent], fixture);
|
||||
});
|
||||
|
||||
test("adds trailing slashes to keys for subtrees", async () => {
|
||||
const tree = new MapTree([
|
||||
["a", 1],
|
||||
["subtree", new MapTree([["b", 2]])],
|
||||
]);
|
||||
const keys = Array.from(await tree.keys());
|
||||
assert.deepEqual(keys, ["a", "subtree/"]);
|
||||
});
|
||||
|
||||
test("can retrieve values with optional trailing slash", async () => {
|
||||
const subtree = new MapTree([["b", 2]]);
|
||||
const tree = new MapTree([
|
||||
["a", 1],
|
||||
["subtree", subtree],
|
||||
]);
|
||||
assert.equal(await tree.get("a"), 1);
|
||||
assert.equal(await tree.get("a/"), 1);
|
||||
assert.equal(await tree.get("subtree"), subtree);
|
||||
assert.equal(await tree.get("subtree/"), subtree);
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.equal(await fixture.get("d"), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture() {
|
||||
const map = new Map([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
]);
|
||||
return new MapTree(map);
|
||||
}
|
||||
156
node_modules/@weborigami/async-tree/test/drivers/ObjectTree.test.js
generated
vendored
Normal file
156
node_modules/@weborigami/async-tree/test/drivers/ObjectTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { ObjectTree, Tree } from "../../src/internal.js";
|
||||
import * as symbols from "../../src/symbols.js";
|
||||
|
||||
describe("ObjectTree", () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.deepEqual(Array.from(await fixture.keys()), [
|
||||
"Alice.md",
|
||||
"Bob.md",
|
||||
"Carol.md",
|
||||
]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const fixture = createFixture();
|
||||
const alice = await fixture.get("Alice.md");
|
||||
assert.equal(alice, "Hello, **Alice**.");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = createFixture();
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const fixture = createFixture();
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test("can set a value", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
});
|
||||
|
||||
// Update existing key
|
||||
await tree.set("a", 4);
|
||||
|
||||
// Delete key
|
||||
await tree.set("b", undefined);
|
||||
|
||||
// Overwrite key with trailing slash
|
||||
await tree.set("c/", {});
|
||||
|
||||
// New key
|
||||
await tree.set("d", 5);
|
||||
|
||||
assert.deepEqual(await Tree.entries(tree), [
|
||||
["a", 4],
|
||||
["c/", {}],
|
||||
["d", 5],
|
||||
]);
|
||||
});
|
||||
|
||||
test("can wrap a class instance", async () => {
|
||||
class Foo {
|
||||
constructor() {
|
||||
this.a = 1;
|
||||
}
|
||||
|
||||
get prop() {
|
||||
return this._prop;
|
||||
}
|
||||
set prop(prop) {
|
||||
this._prop = prop;
|
||||
}
|
||||
}
|
||||
class Bar extends Foo {
|
||||
method() {}
|
||||
}
|
||||
const bar = new Bar();
|
||||
/** @type {any} */ (bar).extra = "Hello";
|
||||
const fixture = new ObjectTree(bar);
|
||||
assert.deepEqual(await Tree.entries(fixture), [
|
||||
["a", 1],
|
||||
["extra", "Hello"],
|
||||
["prop", undefined],
|
||||
]);
|
||||
assert.equal(await fixture.get("a"), 1);
|
||||
await fixture.set("prop", "Goodbye");
|
||||
assert.equal(bar.prop, "Goodbye");
|
||||
assert.equal(await fixture.get("prop"), "Goodbye");
|
||||
});
|
||||
|
||||
test("sets parent symbol on subobjects", async () => {
|
||||
const fixture = new ObjectTree({
|
||||
sub: {},
|
||||
});
|
||||
const sub = await fixture.get("sub");
|
||||
assert.equal(sub[symbols.parent], fixture);
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const fixture = new ObjectTree({
|
||||
a: 1,
|
||||
more: new ObjectTree({
|
||||
b: 2,
|
||||
}),
|
||||
});
|
||||
const more = await fixture.get("more");
|
||||
assert.equal(more.parent, fixture);
|
||||
});
|
||||
|
||||
test("adds trailing slashes to keys for subtrees", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a1: 1,
|
||||
a2: new ObjectTree({
|
||||
b1: 2,
|
||||
}),
|
||||
a3: 3,
|
||||
a4: new ObjectTree({
|
||||
b2: 4,
|
||||
}),
|
||||
});
|
||||
const keys = Array.from(await tree.keys());
|
||||
assert.deepEqual(keys, ["a1", "a2/", "a3", "a4/"]);
|
||||
});
|
||||
|
||||
test("can retrieve values with optional trailing slash", async () => {
|
||||
const subtree = {
|
||||
async get(key) {},
|
||||
async keys() {},
|
||||
};
|
||||
const tree = new ObjectTree({
|
||||
a: 1,
|
||||
subtree,
|
||||
});
|
||||
assert.equal(await tree.get("a"), 1);
|
||||
assert.equal(await tree.get("a/"), 1);
|
||||
assert.equal(await tree.get("subtree"), subtree);
|
||||
assert.equal(await tree.get("subtree/"), subtree);
|
||||
});
|
||||
|
||||
test("method on an object is bound to the object", async () => {
|
||||
const n = new Number(123);
|
||||
const tree = new ObjectTree(n);
|
||||
const method = await tree.get("toString");
|
||||
assert.equal(method(), "123");
|
||||
});
|
||||
});
|
||||
|
||||
function createFixture() {
|
||||
return new ObjectTree({
|
||||
"Alice.md": "Hello, **Alice**.",
|
||||
"Bob.md": "Hello, **Bob**.",
|
||||
"Carol.md": "Hello, **Carol**.",
|
||||
});
|
||||
}
|
||||
44
node_modules/@weborigami/async-tree/test/drivers/SetTree.test.js
generated
vendored
Normal file
44
node_modules/@weborigami/async-tree/test/drivers/SetTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import SetTree from "../../src/drivers/SetTree.js";
|
||||
import { ObjectTree } from "../../src/internal.js";
|
||||
|
||||
describe("SetTree", () => {
|
||||
test("can get the keys of the tree", async () => {
|
||||
const set = new Set(["a", "b", "c"]);
|
||||
const fixture = new SetTree(set);
|
||||
assert.deepEqual(Array.from(await fixture.keys()), [0, 1, 2]);
|
||||
});
|
||||
|
||||
test("can get the value for a key", async () => {
|
||||
const set = new Set(["a", "b", "c"]);
|
||||
const fixture = new SetTree(set);
|
||||
const a = await fixture.get(0);
|
||||
assert.equal(a, "a");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const set = new Set(["a", "b", "c"]);
|
||||
const fixture = new SetTree(set);
|
||||
assert.equal(await fixture.get(3), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const set = new Set(["a", "b", "c"]);
|
||||
const fixture = new SetTree(set);
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test("sets parent on subtrees", async () => {
|
||||
const set = new Set();
|
||||
set.add(new ObjectTree({}));
|
||||
const fixture = new SetTree(set);
|
||||
const subtree = await fixture.get(0);
|
||||
assert.equal(subtree.parent, fixture);
|
||||
});
|
||||
});
|
||||
92
node_modules/@weborigami/async-tree/test/drivers/SiteTree.test.js
generated
vendored
Normal file
92
node_modules/@weborigami/async-tree/test/drivers/SiteTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import assert from "node:assert";
|
||||
import { beforeEach, describe, mock, test } from "node:test";
|
||||
import SiteTree from "../../src/drivers/SiteTree.js";
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
const mockHost = "https://mock";
|
||||
|
||||
const mockResponses = {
|
||||
"/about": {
|
||||
redirected: true,
|
||||
status: 301,
|
||||
url: "https://mock/about/",
|
||||
},
|
||||
"/about/Alice.html": {
|
||||
data: "Hello, Alice!",
|
||||
},
|
||||
"/about/Bob.html": {
|
||||
data: "Hello, Bob!",
|
||||
},
|
||||
"/about/Carol.html": {
|
||||
data: "Hello, Carol!",
|
||||
},
|
||||
"/index.html": {
|
||||
data: "Home page",
|
||||
},
|
||||
};
|
||||
|
||||
describe("SiteTree", () => {
|
||||
beforeEach(() => {
|
||||
mock.method(global, "fetch", mockFetch);
|
||||
});
|
||||
|
||||
test("returns an empty array as the keys of a tree", async () => {
|
||||
const fixture = new SiteTree(mockHost);
|
||||
const keys = await fixture.keys();
|
||||
assert.deepEqual(Array.from(keys), []);
|
||||
});
|
||||
|
||||
test("can get a plain value for a key", async () => {
|
||||
const fixture = new SiteTree(mockHost);
|
||||
const arrayBuffer = await fixture.get("index.html");
|
||||
const text = textDecoder.decode(arrayBuffer);
|
||||
assert.equal(text, "Home page");
|
||||
});
|
||||
|
||||
test("immediately return a new tree for a key with a trailing slash", async () => {
|
||||
const fixture = new SiteTree(mockHost);
|
||||
const about = await fixture.get("about/");
|
||||
assert(about instanceof SiteTree);
|
||||
assert.equal(about.href, "https://mock/about/");
|
||||
});
|
||||
|
||||
test("getting an unsupported key returns undefined", async () => {
|
||||
const fixture = new SiteTree(mockHost);
|
||||
assert.equal(await fixture.get("xyz"), undefined);
|
||||
});
|
||||
|
||||
test("getting a null/undefined key throws an exception", async () => {
|
||||
const fixture = new SiteTree(mockHost);
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(null);
|
||||
});
|
||||
await assert.rejects(async () => {
|
||||
await fixture.get(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function mockFetch(href) {
|
||||
if (!href.startsWith(mockHost)) {
|
||||
return { status: 404 };
|
||||
}
|
||||
const path = href.slice(mockHost.length);
|
||||
const mockedResponse = mockResponses[path];
|
||||
if (mockedResponse) {
|
||||
return Object.assign(
|
||||
{
|
||||
arrayBuffer: () => textEncoder.encode(mockedResponse.data).buffer,
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: () => mockedResponse.data,
|
||||
},
|
||||
mockedResponse
|
||||
);
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
};
|
||||
}
|
||||
121
node_modules/@weborigami/async-tree/test/drivers/calendarTree.test.js
generated
vendored
Normal file
121
node_modules/@weborigami/async-tree/test/drivers/calendarTree.test.js
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import calendar from "../../src/drivers/calendarTree.js";
|
||||
import { toPlainValue } from "../../src/utilities.js";
|
||||
|
||||
describe("calendarTree", () => {
|
||||
test("without a start or end, returns a tree for today", async () => {
|
||||
const tree = calendar({
|
||||
value: (year, month, day) => `${year}-${month}-${day}`,
|
||||
});
|
||||
const plain = await toPlainValue(tree);
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
const month = (today.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = today.getDate().toString().padStart(2, "0");
|
||||
assert.deepEqual(plain, {
|
||||
[year]: {
|
||||
[month]: {
|
||||
[day]: `${year}-${month}-${day}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns a tree for a month range", async () => {
|
||||
const tree = calendar({
|
||||
start: "2025-01",
|
||||
end: "2025-02",
|
||||
value: (year, month, day) => `${year}-${month}-${day}`,
|
||||
});
|
||||
const plain = await toPlainValue(tree);
|
||||
assert.deepEqual(plain, {
|
||||
2025: {
|
||||
"01": {
|
||||
"01": "2025-01-01",
|
||||
"02": "2025-01-02",
|
||||
"03": "2025-01-03",
|
||||
"04": "2025-01-04",
|
||||
"05": "2025-01-05",
|
||||
"06": "2025-01-06",
|
||||
"07": "2025-01-07",
|
||||
"08": "2025-01-08",
|
||||
"09": "2025-01-09",
|
||||
10: "2025-01-10",
|
||||
11: "2025-01-11",
|
||||
12: "2025-01-12",
|
||||
13: "2025-01-13",
|
||||
14: "2025-01-14",
|
||||
15: "2025-01-15",
|
||||
16: "2025-01-16",
|
||||
17: "2025-01-17",
|
||||
18: "2025-01-18",
|
||||
19: "2025-01-19",
|
||||
20: "2025-01-20",
|
||||
21: "2025-01-21",
|
||||
22: "2025-01-22",
|
||||
23: "2025-01-23",
|
||||
24: "2025-01-24",
|
||||
25: "2025-01-25",
|
||||
26: "2025-01-26",
|
||||
27: "2025-01-27",
|
||||
28: "2025-01-28",
|
||||
29: "2025-01-29",
|
||||
30: "2025-01-30",
|
||||
31: "2025-01-31",
|
||||
},
|
||||
"02": {
|
||||
"01": "2025-02-01",
|
||||
"02": "2025-02-02",
|
||||
"03": "2025-02-03",
|
||||
"04": "2025-02-04",
|
||||
"05": "2025-02-05",
|
||||
"06": "2025-02-06",
|
||||
"07": "2025-02-07",
|
||||
"08": "2025-02-08",
|
||||
"09": "2025-02-09",
|
||||
10: "2025-02-10",
|
||||
11: "2025-02-11",
|
||||
12: "2025-02-12",
|
||||
13: "2025-02-13",
|
||||
14: "2025-02-14",
|
||||
15: "2025-02-15",
|
||||
16: "2025-02-16",
|
||||
17: "2025-02-17",
|
||||
18: "2025-02-18",
|
||||
19: "2025-02-19",
|
||||
20: "2025-02-20",
|
||||
21: "2025-02-21",
|
||||
22: "2025-02-22",
|
||||
23: "2025-02-23",
|
||||
24: "2025-02-24",
|
||||
25: "2025-02-25",
|
||||
26: "2025-02-26",
|
||||
27: "2025-02-27",
|
||||
28: "2025-02-28",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("returns a tree for a day range", async () => {
|
||||
const tree = calendar({
|
||||
start: "2025-02-27",
|
||||
end: "2025-03-02",
|
||||
value: (year, month, day) => `${year}-${month}-${day}`,
|
||||
});
|
||||
const plain = await toPlainValue(tree);
|
||||
assert.deepEqual(plain, {
|
||||
2025: {
|
||||
"02": {
|
||||
27: "2025-02-27",
|
||||
28: "2025-02-28",
|
||||
},
|
||||
"03": {
|
||||
"01": "2025-03-01",
|
||||
"02": "2025-03-02",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Alice.md
generated
vendored
Normal file
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Alice.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello, **Alice**.
|
||||
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Bob.md
generated
vendored
Normal file
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Bob.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello, **Bob**.
|
||||
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Carol.md
generated
vendored
Normal file
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/Carol.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello, **Carol**.
|
||||
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/subfolder/README.md
generated
vendored
Normal file
1
node_modules/@weborigami/async-tree/test/drivers/fixtures/markdown/subfolder/README.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This file exists to force the creation of the parent folder.
|
||||
41
node_modules/@weborigami/async-tree/test/extension.test.js
generated
vendored
Normal file
41
node_modules/@weborigami/async-tree/test/extension.test.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { extname, match, replace } from "../src/extension.js";
|
||||
|
||||
describe("extension", () => {
|
||||
test("extname", () => {
|
||||
assert.equal(extname(".\\"), "");
|
||||
assert.equal(extname("..\\"), ".\\");
|
||||
assert.equal(extname("file.ext\\"), ".ext\\");
|
||||
assert.equal(extname("file.ext\\\\"), ".ext\\\\");
|
||||
assert.equal(extname("file\\"), "");
|
||||
assert.equal(extname("file\\\\"), "");
|
||||
assert.equal(extname("file.\\"), ".\\");
|
||||
assert.equal(extname("file.\\\\"), ".\\\\");
|
||||
});
|
||||
|
||||
test("match", () => {
|
||||
assert.equal(match("file.md", ".md"), "file");
|
||||
assert.equal(match("file.md", ".txt"), null);
|
||||
assert.equal(match("file.md/", ".md"), "file/");
|
||||
assert.equal(match("file", ""), "file");
|
||||
assert.equal(match("file", "/"), null);
|
||||
assert.equal(match("file/", "/"), "file");
|
||||
});
|
||||
|
||||
test("match can handle multi-part extensions", () => {
|
||||
assert.equal(match("foo.ori.html", ".ori.html"), "foo");
|
||||
assert.equal(match("foo.ori.html", ".html"), "foo.ori");
|
||||
assert.equal(match("foo.ori.html", ".txt"), null);
|
||||
assert.equal(match("foo.ori.html/", ".ori.html"), "foo/");
|
||||
});
|
||||
|
||||
test("replace", () => {
|
||||
assert.equal(replace("file.md", ".md", ".html"), "file.html");
|
||||
assert.equal(replace("file.md", ".txt", ".html"), "file.md");
|
||||
assert.equal(replace("file.md/", ".md", ".html"), "file.html/");
|
||||
assert.equal(replace("folder/", "", ".html"), "folder.html");
|
||||
assert.equal(replace("folder", "/", ".html"), "folder");
|
||||
assert.equal(replace("folder/", "/", ".html"), "folder.html");
|
||||
});
|
||||
});
|
||||
15
node_modules/@weborigami/async-tree/test/jsonKeys.test.js
generated
vendored
Normal file
15
node_modules/@weborigami/async-tree/test/jsonKeys.test.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { DeepObjectTree } from "@weborigami/async-tree";
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import * as jsonKeys from "../src/jsonKeys.js";
|
||||
|
||||
describe("jsonKeys", () => {
|
||||
test("stringifies JSON Keys", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
about: {},
|
||||
"index.html": "Home",
|
||||
});
|
||||
const json = await jsonKeys.stringify(tree);
|
||||
assert.strictEqual(json, '["about/","index.html"]');
|
||||
});
|
||||
});
|
||||
63
node_modules/@weborigami/async-tree/test/operations/cache.test.js
generated
vendored
Normal file
63
node_modules/@weborigami/async-tree/test/operations/cache.test.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
||||
import cache from "../../src/operations/cache.js";
|
||||
|
||||
describe("cache", () => {
|
||||
test("caches reads of values from one tree into another", async () => {
|
||||
const objectCache = new ObjectTree({});
|
||||
const fixture = cache(
|
||||
new DeepObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
more: {
|
||||
d: 4,
|
||||
},
|
||||
}),
|
||||
objectCache
|
||||
);
|
||||
|
||||
const keys = [...(await fixture.keys())];
|
||||
assert.deepEqual(keys, ["a", "b", "c", "more/"]);
|
||||
|
||||
assert.equal(await objectCache.get("a"), undefined);
|
||||
assert.equal(await fixture.get("a"), 1);
|
||||
assert.equal(await objectCache.get("a"), 1); // Now in cache
|
||||
|
||||
assert.equal(await objectCache.get("b"), undefined);
|
||||
assert.equal(await fixture.get("b"), 2);
|
||||
assert.equal(await objectCache.get("b"), 2);
|
||||
|
||||
assert.equal(await objectCache.get("more"), undefined);
|
||||
const more = await fixture.get("more");
|
||||
assert.deepEqual([...(await more.keys())], ["d"]);
|
||||
assert.equal(await more.get("d"), 4);
|
||||
const moreCache = await objectCache.get("more");
|
||||
assert.equal(await moreCache.get("d"), 4);
|
||||
});
|
||||
|
||||
test("if a cache filter is supplied, it only caches values whose keys match the filter", async () => {
|
||||
const objectCache = new ObjectTree({});
|
||||
const fixture = cache(
|
||||
Tree.from({
|
||||
"a.txt": "a",
|
||||
"b.txt": "b",
|
||||
}),
|
||||
objectCache,
|
||||
Tree.from({
|
||||
"a.txt": true,
|
||||
})
|
||||
);
|
||||
|
||||
// Access some values to populate the cache.
|
||||
assert.equal(await fixture.get("a.txt"), "a");
|
||||
assert.equal(await fixture.get("b.txt"), "b");
|
||||
|
||||
// The a.txt value should be cached because it matches the filter.
|
||||
assert.equal(await objectCache.get("a.txt"), "a");
|
||||
|
||||
// The b.txt value should not be cached because it does not match the filter.
|
||||
assert.equal(await objectCache.get("b.txt"), undefined);
|
||||
});
|
||||
});
|
||||
90
node_modules/@weborigami/async-tree/test/operations/cachedKeyFunctions.test.js
generated
vendored
Normal file
90
node_modules/@weborigami/async-tree/test/operations/cachedKeyFunctions.test.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, ObjectTree } from "../../src/internal.js";
|
||||
import cachedKeyFunctions from "../../src/operations/cachedKeyFunctions.js";
|
||||
|
||||
describe("cachedKeyFunctions", () => {
|
||||
test("maps keys with caching", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
});
|
||||
|
||||
let callCount = 0;
|
||||
const addUnderscore = async (sourceKey, tree) => {
|
||||
callCount++;
|
||||
return `_${sourceKey}`;
|
||||
};
|
||||
|
||||
const { inverseKey, key } = cachedKeyFunctions(addUnderscore);
|
||||
|
||||
assert.equal(await inverseKey("_a", tree), "a"); // Cache miss
|
||||
assert.equal(callCount, 1);
|
||||
assert.equal(await inverseKey("_a", tree), "a");
|
||||
assert.equal(callCount, 1);
|
||||
assert.equal(await inverseKey("_b", tree), "b"); // Cache miss
|
||||
assert.equal(callCount, 2);
|
||||
|
||||
assert.equal(await key("a", tree), "_a");
|
||||
assert.equal(await key("a", tree), "_a");
|
||||
assert.equal(await key("b", tree), "_b");
|
||||
assert.equal(callCount, 2);
|
||||
|
||||
// `c` isn't in tree, so we should get undefined.
|
||||
assert.equal(await inverseKey("_c", tree), undefined);
|
||||
// But key mapping is still possible.
|
||||
assert.equal(await key("c", tree), "_c");
|
||||
// And now we have a cache hit.
|
||||
assert.equal(await inverseKey("_c", tree), "c");
|
||||
assert.equal(callCount, 3);
|
||||
});
|
||||
|
||||
test("maps keys with caching and deep option", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: "letter a",
|
||||
b: {
|
||||
c: "letter c",
|
||||
},
|
||||
});
|
||||
|
||||
let callCount = 0;
|
||||
const addUnderscore = async (sourceKey, tree) => {
|
||||
callCount++;
|
||||
return `_${sourceKey}`;
|
||||
};
|
||||
|
||||
const { inverseKey, key } = cachedKeyFunctions(addUnderscore, true);
|
||||
|
||||
assert.equal(await inverseKey("_a", tree), "a"); // Cache miss
|
||||
assert.equal(await inverseKey("_a", tree), "a");
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
// Subtree key left alone
|
||||
assert.equal(await inverseKey("_b", tree), undefined);
|
||||
assert.equal(await inverseKey("b", tree), "b");
|
||||
assert.equal(await inverseKey("b/", tree), "b/");
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
assert.equal(await key("a", tree), "_a");
|
||||
assert.equal(await key("a", tree), "_a");
|
||||
assert.equal(callCount, 1);
|
||||
|
||||
assert.equal(await key("b/", tree), "b/");
|
||||
assert.equal(await key("b", tree), "b");
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
|
||||
test("preserves trailing slashes", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: "letter a",
|
||||
});
|
||||
const addUnderscore = async (sourceKey) => `_${sourceKey}`;
|
||||
const { inverseKey, key } = cachedKeyFunctions(addUnderscore);
|
||||
|
||||
assert.equal(await key("a/", tree), "_a/");
|
||||
assert.equal(await key("a", tree), "_a");
|
||||
|
||||
assert.equal(await inverseKey("_a/", tree), "a/");
|
||||
assert.equal(await inverseKey("_a", tree), "a");
|
||||
});
|
||||
});
|
||||
34
node_modules/@weborigami/async-tree/test/operations/concat.test.js
generated
vendored
Normal file
34
node_modules/@weborigami/async-tree/test/operations/concat.test.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import FunctionTree from "../../src/drivers/FunctionTree.js";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import concat from "../../src/operations/concat.js";
|
||||
|
||||
describe("concat", () => {
|
||||
test("concatenates deep tree values", async () => {
|
||||
const tree = Tree.from({
|
||||
a: "A",
|
||||
b: "B",
|
||||
c: "C",
|
||||
more: {
|
||||
d: "D",
|
||||
e: "E",
|
||||
},
|
||||
});
|
||||
const result = await concat.call(null, tree);
|
||||
assert.equal(result, "ABCDE");
|
||||
});
|
||||
|
||||
test("concatenates deep tree-like values", async () => {
|
||||
const letters = ["a", "b", "c"];
|
||||
const specimens = new FunctionTree(
|
||||
(letter) => ({
|
||||
lowercase: letter,
|
||||
uppercase: letter.toUpperCase(),
|
||||
}),
|
||||
letters
|
||||
);
|
||||
const result = await concat.call(null, specimens);
|
||||
assert.equal(result, "aAbBcC");
|
||||
});
|
||||
});
|
||||
42
node_modules/@weborigami/async-tree/test/operations/deepMerge.test.js
generated
vendored
Normal file
42
node_modules/@weborigami/async-tree/test/operations/deepMerge.test.js
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, Tree } from "../../src/internal.js";
|
||||
import mergeDeep from "../../src/operations/deepMerge.js";
|
||||
|
||||
describe("mergeDeep", () => {
|
||||
test("can merge deep", async () => {
|
||||
const fixture = mergeDeep(
|
||||
new DeepObjectTree({
|
||||
a: {
|
||||
b: 0, // Will be obscured by `b` below
|
||||
c: {
|
||||
d: 2,
|
||||
},
|
||||
},
|
||||
}),
|
||||
new DeepObjectTree({
|
||||
a: {
|
||||
b: 1,
|
||||
c: {
|
||||
e: 3,
|
||||
},
|
||||
f: 4,
|
||||
},
|
||||
})
|
||||
);
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
a: {
|
||||
b: 1,
|
||||
c: {
|
||||
d: 2,
|
||||
e: 3,
|
||||
},
|
||||
f: 4,
|
||||
},
|
||||
});
|
||||
|
||||
// Parent of a subvalue is the merged tree
|
||||
const a = await fixture.get("a");
|
||||
assert.equal(a.parent, fixture);
|
||||
});
|
||||
});
|
||||
24
node_modules/@weborigami/async-tree/test/operations/deepReverse.test.js
generated
vendored
Normal file
24
node_modules/@weborigami/async-tree/test/operations/deepReverse.test.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import deepReverse from "../../src/operations/deepReverse.js";
|
||||
|
||||
describe("deepReverse", () => {
|
||||
test("reverses keys at all levels of a tree", async () => {
|
||||
const tree = {
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: 3,
|
||||
},
|
||||
};
|
||||
const reversed = deepReverse.call(null, tree);
|
||||
assert.deepEqual(await Tree.plain(reversed), {
|
||||
b: {
|
||||
d: 3,
|
||||
c: 2,
|
||||
},
|
||||
a: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
22
node_modules/@weborigami/async-tree/test/operations/deepTake.test.js
generated
vendored
Normal file
22
node_modules/@weborigami/async-tree/test/operations/deepTake.test.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import deepTake from "../../src/operations/deepTake.js";
|
||||
|
||||
describe("deepTake", () => {
|
||||
test("traverses deeply and returns a limited number of items", async () => {
|
||||
const tree = {
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: {
|
||||
e: 3,
|
||||
},
|
||||
f: 4,
|
||||
},
|
||||
g: 5,
|
||||
};
|
||||
const result = await deepTake(tree, 4);
|
||||
assert.deepEqual(await Tree.plain(result), [1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
28
node_modules/@weborigami/async-tree/test/operations/deepValues.test.js
generated
vendored
Normal file
28
node_modules/@weborigami/async-tree/test/operations/deepValues.test.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import deepValues from "../../src/operations/deepValues.js";
|
||||
|
||||
describe("deepValues", () => {
|
||||
test("returns in-order array of a tree's values", async () => {
|
||||
const tree = {
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: {
|
||||
e: 3,
|
||||
},
|
||||
},
|
||||
f: {
|
||||
g: 4,
|
||||
},
|
||||
};
|
||||
const values = await deepValues(tree);
|
||||
assert.deepEqual(values, [1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test("returns in-order array of values in nested arrays", async () => {
|
||||
const tree = [1, [2, 3], 4];
|
||||
const values = await deepValues(tree);
|
||||
assert.deepEqual(values, [1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
23
node_modules/@weborigami/async-tree/test/operations/deepValuesIterator.test.js
generated
vendored
Normal file
23
node_modules/@weborigami/async-tree/test/operations/deepValuesIterator.test.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { ObjectTree } from "../../src/internal.js";
|
||||
import deepValuesIterator from "../../src/operations/deepValuesIterator.js";
|
||||
|
||||
describe("deepValuesIterator", () => {
|
||||
test("returns an iterator of a tree's deep values", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: 1,
|
||||
b: 2,
|
||||
more: {
|
||||
c: 3,
|
||||
d: 4,
|
||||
},
|
||||
});
|
||||
const values = [];
|
||||
// The tree will be shallow, but we'll ask to expand the values.
|
||||
for await (const value of deepValuesIterator(tree, { expand: true })) {
|
||||
values.push(value);
|
||||
}
|
||||
assert.deepEqual(values, [1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
54
node_modules/@weborigami/async-tree/test/operations/group.test.js
generated
vendored
Normal file
54
node_modules/@weborigami/async-tree/test/operations/group.test.js
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import group from "../../src/operations/group.js";
|
||||
|
||||
describe("group transform", () => {
|
||||
test("groups an array using a group key function", async () => {
|
||||
const fonts = [
|
||||
{ name: "Aboreto", tags: ["Sans Serif"] },
|
||||
{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] },
|
||||
{ name: "Alegreya", tags: ["Serif"] },
|
||||
{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
|
||||
];
|
||||
const tree = Tree.from(fonts);
|
||||
const grouped = await group(tree, (value, key, tree) => value.tags);
|
||||
assert.deepEqual(await Tree.plain(grouped), {
|
||||
Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
|
||||
Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
|
||||
"Sans Serif": [
|
||||
{ name: "Aboreto", tags: ["Sans Serif"] },
|
||||
{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] },
|
||||
{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
|
||||
],
|
||||
Serif: [{ name: "Alegreya", tags: ["Serif"] }],
|
||||
});
|
||||
});
|
||||
|
||||
test("groups an object using a group key function", async () => {
|
||||
const fonts = {
|
||||
Aboreto: { tags: ["Sans Serif"] },
|
||||
"Albert Sans": { tags: ["Geometric", "Sans Serif"] },
|
||||
Alegreya: { tags: ["Serif"] },
|
||||
"Work Sans": { tags: ["Grotesque", "Sans Serif"] },
|
||||
};
|
||||
const tree = Tree.from(fonts);
|
||||
const grouped = await group(tree, (value, key, tree) => value.tags);
|
||||
assert.deepEqual(await Tree.plain(grouped), {
|
||||
Geometric: {
|
||||
"Albert Sans": { tags: ["Geometric", "Sans Serif"] },
|
||||
},
|
||||
Grotesque: {
|
||||
"Work Sans": { tags: ["Grotesque", "Sans Serif"] },
|
||||
},
|
||||
"Sans Serif": {
|
||||
Aboreto: { tags: ["Sans Serif"] },
|
||||
"Albert Sans": { tags: ["Geometric", "Sans Serif"] },
|
||||
"Work Sans": { tags: ["Grotesque", "Sans Serif"] },
|
||||
},
|
||||
Serif: {
|
||||
Alegreya: { tags: ["Serif"] },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
17
node_modules/@weborigami/async-tree/test/operations/invokeFunctions.test.js
generated
vendored
Normal file
17
node_modules/@weborigami/async-tree/test/operations/invokeFunctions.test.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import invokeFunctions from "../../src/operations/invokeFunctions.js";
|
||||
|
||||
describe("invokeFunctions", () => {
|
||||
test("invokes function values, leaves other values as is", async () => {
|
||||
const fixture = invokeFunctions({
|
||||
a: 1,
|
||||
b: () => 2,
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
a: 1,
|
||||
b: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
82
node_modules/@weborigami/async-tree/test/operations/keyFunctionsForExtensions.test.js
generated
vendored
Normal file
82
node_modules/@weborigami/async-tree/test/operations/keyFunctionsForExtensions.test.js
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { ObjectTree, Tree } from "../../src/internal.js";
|
||||
import keyFunctionsForExtensions from "../../src/operations/keyFunctionsForExtensions.js";
|
||||
import map from "../../src/operations/map.js";
|
||||
|
||||
describe("keyMapsForExtensions", () => {
|
||||
test("returns key functions that pass a matching key through", async () => {
|
||||
const { inverseKey, key } = keyFunctionsForExtensions({
|
||||
sourceExtension: ".txt",
|
||||
});
|
||||
assert.equal(await inverseKey("file.txt"), "file.txt");
|
||||
assert.equal(await inverseKey("file.txt/"), "file.txt");
|
||||
assert.equal(await key("file.txt"), "file.txt");
|
||||
assert.equal(await key("file.txt/"), "file.txt/");
|
||||
assert.equal(await inverseKey("file.foo"), undefined);
|
||||
assert.equal(await key("file.foo"), undefined);
|
||||
});
|
||||
|
||||
test("returns key functions that can map extensions", async () => {
|
||||
const { inverseKey, key } = keyFunctionsForExtensions({
|
||||
resultExtension: ".json",
|
||||
sourceExtension: ".md",
|
||||
});
|
||||
assert.equal(await inverseKey("file.json"), "file.md");
|
||||
assert.equal(await inverseKey("file.json/"), "file.md");
|
||||
assert.equal(await key("file.md"), "file.json");
|
||||
assert.equal(await key("file.md/"), "file.json/");
|
||||
assert.equal(await inverseKey("file.foo"), undefined);
|
||||
assert.equal(await key("file.foo"), undefined);
|
||||
});
|
||||
|
||||
test("key functions can handle a slash as an explicit extension", async () => {
|
||||
const { inverseKey, key } = keyFunctionsForExtensions({
|
||||
resultExtension: ".html",
|
||||
sourceExtension: "/",
|
||||
});
|
||||
assert.equal(await inverseKey("file.html"), "file/");
|
||||
assert.equal(await inverseKey("file.html/"), "file/");
|
||||
assert.equal(await key("file"), undefined);
|
||||
assert.equal(await key("file/"), "file.html");
|
||||
});
|
||||
|
||||
test("works with map to handle keys that end in a given resultExtension", async () => {
|
||||
const files = new ObjectTree({
|
||||
"file1.txt": "will be mapped",
|
||||
file2: "won't be mapped",
|
||||
"file3.foo": "won't be mapped",
|
||||
});
|
||||
const { inverseKey, key } = keyFunctionsForExtensions({
|
||||
sourceExtension: ".txt",
|
||||
});
|
||||
const fixture = map(files, {
|
||||
inverseKey,
|
||||
key,
|
||||
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
"file1.txt": "WILL BE MAPPED",
|
||||
});
|
||||
});
|
||||
|
||||
test("works with map to change a key's resultExtension", async () => {
|
||||
const files = new ObjectTree({
|
||||
"file1.txt": "will be mapped",
|
||||
file2: "won't be mapped",
|
||||
"file3.foo": "won't be mapped",
|
||||
});
|
||||
const { inverseKey, key } = keyFunctionsForExtensions({
|
||||
resultExtension: ".upper",
|
||||
sourceExtension: ".txt",
|
||||
});
|
||||
const fixture = map(files, {
|
||||
inverseKey,
|
||||
key,
|
||||
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
"file1.upper": "WILL BE MAPPED",
|
||||
});
|
||||
});
|
||||
});
|
||||
204
node_modules/@weborigami/async-tree/test/operations/map.test.js
generated
vendored
Normal file
204
node_modules/@weborigami/async-tree/test/operations/map.test.js
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import FunctionTree from "../../src/drivers/FunctionTree.js";
|
||||
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
||||
import map from "../../src/operations/map.js";
|
||||
import * as trailingSlash from "../../src/trailingSlash.js";
|
||||
|
||||
describe("map", () => {
|
||||
test("returns identity graph if no key or value function is supplied", async () => {
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
};
|
||||
const mapped = map(tree, {});
|
||||
assert.deepEqual(await Tree.plain(mapped), {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
});
|
||||
});
|
||||
|
||||
test("maps values", async () => {
|
||||
const tree = new ObjectTree({
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
c: undefined, // Won't be mapped
|
||||
});
|
||||
const mapped = map(tree, {
|
||||
value: (sourceValue, sourceKey, innerTree) => {
|
||||
assert(sourceKey === "a" || sourceKey === "b");
|
||||
assert.equal(innerTree, tree);
|
||||
return sourceValue.toUpperCase();
|
||||
},
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(mapped), {
|
||||
a: "LETTER A",
|
||||
b: "LETTER B",
|
||||
c: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test("interprets a single function argument as the value function", async () => {
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
};
|
||||
const uppercaseValues = map(tree, (sourceValue, sourceKey, tree) => {
|
||||
assert(sourceKey === "a" || sourceKey === "b");
|
||||
return sourceValue.toUpperCase();
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(uppercaseValues), {
|
||||
a: "LETTER A",
|
||||
b: "LETTER B",
|
||||
});
|
||||
});
|
||||
|
||||
test("maps keys using key and inverseKey", async () => {
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
};
|
||||
const underscoreKeys = map(tree, {
|
||||
key: addUnderscore,
|
||||
inverseKey: removeUnderscore,
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(underscoreKeys), {
|
||||
_a: "letter a",
|
||||
_b: "letter b",
|
||||
});
|
||||
});
|
||||
|
||||
test("maps keys and values", async () => {
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
};
|
||||
const underscoreKeysUppercaseValues = map(tree, {
|
||||
key: addUnderscore,
|
||||
inverseKey: removeUnderscore,
|
||||
value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(underscoreKeysUppercaseValues), {
|
||||
_a: "LETTER A",
|
||||
_b: "LETTER B",
|
||||
});
|
||||
});
|
||||
|
||||
test("a shallow map is applied to async subtrees too", async () => {
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
more: {
|
||||
b: "letter b",
|
||||
},
|
||||
};
|
||||
const underscoreKeys = map(tree, {
|
||||
key: async (sourceKey, tree) => `_${sourceKey}`,
|
||||
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
||||
value: async (sourceValue, sourceKey, tree) => sourceKey,
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(underscoreKeys), {
|
||||
_a: "a",
|
||||
_more: "more",
|
||||
});
|
||||
});
|
||||
|
||||
test("value can provide a default key and inverse key functions", async () => {
|
||||
const uppercase = (s) => s.toUpperCase();
|
||||
uppercase.key = addUnderscore;
|
||||
uppercase.inverseKey = removeUnderscore;
|
||||
const tree = {
|
||||
a: "letter a",
|
||||
b: "letter b",
|
||||
};
|
||||
const mapped = map(tree, uppercase);
|
||||
assert.deepEqual(await Tree.plain(mapped), {
|
||||
_a: "LETTER A",
|
||||
_b: "LETTER B",
|
||||
});
|
||||
});
|
||||
|
||||
test("deep maps values", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: "letter a",
|
||||
more: {
|
||||
b: "letter b",
|
||||
},
|
||||
});
|
||||
const uppercaseValues = map(tree, {
|
||||
deep: true,
|
||||
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(uppercaseValues), {
|
||||
a: "LETTER A",
|
||||
more: {
|
||||
b: "LETTER B",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("deep maps leaf keys", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: "letter a",
|
||||
more: {
|
||||
b: "letter b",
|
||||
},
|
||||
});
|
||||
const underscoreKeys = map(tree, {
|
||||
deep: true,
|
||||
key: addUnderscore,
|
||||
inverseKey: removeUnderscore,
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(underscoreKeys), {
|
||||
_a: "letter a",
|
||||
more: {
|
||||
_b: "letter b",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("deep maps leaf keys and values", async () => {
|
||||
const tree = new DeepObjectTree({
|
||||
a: "letter a",
|
||||
more: {
|
||||
b: "letter b",
|
||||
},
|
||||
});
|
||||
const underscoreKeysUppercaseValues = map(tree, {
|
||||
deep: true,
|
||||
key: addUnderscore,
|
||||
inverseKey: removeUnderscore,
|
||||
value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(underscoreKeysUppercaseValues), {
|
||||
_a: "LETTER A",
|
||||
more: {
|
||||
_b: "LETTER B",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("needsSourceValue can be set to false in cases where the value isn't necessary", async () => {
|
||||
let flag = false;
|
||||
const tree = new FunctionTree(() => {
|
||||
flag = true;
|
||||
}, ["a", "b", "c"]);
|
||||
const mapped = map(tree, {
|
||||
needsSourceValue: false,
|
||||
value: () => "X",
|
||||
});
|
||||
assert.deepEqual(await Tree.plain(mapped), {
|
||||
a: "X",
|
||||
b: "X",
|
||||
c: "X",
|
||||
});
|
||||
assert(!flag);
|
||||
});
|
||||
});
|
||||
|
||||
function addUnderscore(key) {
|
||||
return `_${key}`;
|
||||
}
|
||||
|
||||
function removeUnderscore(key) {
|
||||
return trailingSlash.has(key) ? key : key.slice(1);
|
||||
}
|
||||
64
node_modules/@weborigami/async-tree/test/operations/merge.test.js
generated
vendored
Normal file
64
node_modules/@weborigami/async-tree/test/operations/merge.test.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
||||
import merge from "../../src/operations/merge.js";
|
||||
import * as symbols from "../../src/symbols.js";
|
||||
|
||||
describe("merge", () => {
|
||||
test("performs a shallow merge", async () => {
|
||||
const fixture = merge(
|
||||
{
|
||||
a: 1,
|
||||
// Will be obscured by `b` that follows
|
||||
b: {
|
||||
c: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
b: {
|
||||
d: 3,
|
||||
},
|
||||
e: {
|
||||
f: 4,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
a: 1,
|
||||
b: {
|
||||
d: 3,
|
||||
},
|
||||
e: {
|
||||
f: 4,
|
||||
},
|
||||
});
|
||||
|
||||
// Merge is shallow, and last tree wins, so `b/c` doesn't exist
|
||||
const c = await Tree.traverse(fixture, "b", "c");
|
||||
assert.equal(c, undefined);
|
||||
|
||||
// Parent of a subvalue is the merged tree
|
||||
const b = await fixture.get("b");
|
||||
assert.equal(b[symbols.parent], fixture);
|
||||
});
|
||||
|
||||
test("subtree can overwrite a leaf node", async () => {
|
||||
const fixture = merge(
|
||||
new ObjectTree({
|
||||
a: 1,
|
||||
}),
|
||||
new DeepObjectTree({
|
||||
a: {
|
||||
b: 2,
|
||||
},
|
||||
})
|
||||
);
|
||||
assert.deepEqual([...(await fixture.keys())], ["a/"]);
|
||||
assert.deepEqual(await Tree.plain(fixture), {
|
||||
a: {
|
||||
b: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
25
node_modules/@weborigami/async-tree/test/operations/regExpKeys.test.js
generated
vendored
Normal file
25
node_modules/@weborigami/async-tree/test/operations/regExpKeys.test.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { DeepObjectTree, Tree } from "../../src/internal.js";
|
||||
import regExpKeys from "../../src/operations/regExpKeys.js";
|
||||
|
||||
describe("regExpKeys", () => {
|
||||
test("matches keys using regular expressions", async () => {
|
||||
const fixture = await regExpKeys(
|
||||
new DeepObjectTree({
|
||||
a: true,
|
||||
"b.*": true,
|
||||
c: {
|
||||
d: true,
|
||||
"e*": true,
|
||||
},
|
||||
})
|
||||
);
|
||||
assert(await Tree.traverse(fixture, "a"));
|
||||
assert(!(await Tree.traverse(fixture, "alice")));
|
||||
assert(await Tree.traverse(fixture, "bob"));
|
||||
assert(await Tree.traverse(fixture, "brenda"));
|
||||
assert(await Tree.traverse(fixture, "c", "d"));
|
||||
assert(await Tree.traverse(fixture, "c", "eee"));
|
||||
});
|
||||
});
|
||||
23
node_modules/@weborigami/async-tree/test/operations/reverse.test.js
generated
vendored
Normal file
23
node_modules/@weborigami/async-tree/test/operations/reverse.test.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, test } from "node:test";
|
||||
import { Tree } from "../../src/internal.js";
|
||||
import reverse from "../../src/operations/reverse.js";
|
||||
|
||||
describe("reverse", () => {
|
||||
test("reverses a tree's top-level keys", async () => {
|
||||
const tree = {
|
||||
a: "A",
|
||||
b: "B",
|
||||
c: "C",
|
||||
};
|
||||
const reversed = reverse.call(null, tree);
|
||||
// @ts-ignore
|
||||
assert.deepEqual(Array.from(await reversed.keys()), ["c", "b", "a"]);
|
||||
// @ts-ignore
|
||||
assert.deepEqual(await Tree.plain(reversed), {
|
||||
c: "C",
|
||||
b: "B",
|
||||
a: "A",
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user