From 5de30283b1d4b2c82b9aa9782716607cc17f4e1c Mon Sep 17 00:00:00 2001 From: evlist Date: Sat, 26 Nov 2022 19:36:55 +0100 Subject: [PATCH] Reintroducing WebWorkers (pfeeew, Vite makes it tough !) --- package-lock.json | 710 ++++++++++++++++-- package.json | 6 + src/db/gpx.test.ts | 86 +++ src/db/gpx.ts | 269 +++++++ src/db/index.ts | 117 +++ src/db/lib.test.ts | 299 ++++++++ src/db/lib.ts | 52 ++ src/db/rte.test.ts | 47 ++ src/db/rte.ts | 33 + src/db/rtept.test.ts | 58 ++ src/db/rtept.ts | 44 ++ src/db/trk.test.ts | 47 ++ src/db/trk.ts | 42 ++ src/db/trkpt.test.ts | 79 ++ src/db/trkpt.ts | 50 ++ src/db/trkseg.test.ts | 40 + src/db/trkseg.ts | 33 + src/db/types.d.ts | 119 +++ src/db/wpt.test.ts | 58 ++ src/db/wpt.ts | 44 ++ src/index.tsx | 36 +- src/lib/cache.ts | 61 ++ src/lib/docuri/index.js | 133 ++++ src/lib/geo.ts | 35 + src/lib/gpx-parser-builder/LICENSE | 21 + src/lib/gpx-parser-builder/README.md | 91 +++ src/lib/gpx-parser-builder/package.json | 36 + src/lib/gpx-parser-builder/src/bounds.js | 8 + src/lib/gpx-parser-builder/src/copyright.js | 7 + .../src/gpx-parser-builder.d.ts | 13 + src/lib/gpx-parser-builder/src/gpx.js | 79 ++ src/lib/gpx-parser-builder/src/link.js | 8 + src/lib/gpx-parser-builder/src/metadata.js | 29 + src/lib/gpx-parser-builder/src/person.js | 11 + src/lib/gpx-parser-builder/src/route.js | 27 + .../gpx-parser-builder/src/track-segment.js | 13 + src/lib/gpx-parser-builder/src/track.js | 26 + src/lib/gpx-parser-builder/src/utils.js | 23 + src/lib/gpx-parser-builder/src/waypoint.js | 32 + src/lib/gpx.test.ts | 33 + src/lib/gpx.ts | 19 + src/lib/ids.test.ts | 120 +++ src/lib/ids.ts | 51 ++ src/lib/types.d.ts | 34 + src/missing-typedefs/docuri.d.ts | 1 + src/missing-typedefs/gpx-parser-builder.d.ts | 1 + src/workers/dispatcher-main.test.js | 37 + src/workers/dispatcher-main.ts | 53 ++ src/workers/dispatcher-worker.test.js | 33 + src/workers/dispatcher-worker.ts | 42 ++ src/workers/get-worker.ts | 8 + vite.config.ts | 55 +- 52 files changed, 3362 insertions(+), 47 deletions(-) create mode 100644 src/db/gpx.test.ts create mode 100644 src/db/gpx.ts create mode 100644 src/db/index.ts create mode 100644 src/db/lib.test.ts create mode 100644 src/db/lib.ts create mode 100644 src/db/rte.test.ts create mode 100644 src/db/rte.ts create mode 100644 src/db/rtept.test.ts create mode 100644 src/db/rtept.ts create mode 100644 src/db/trk.test.ts create mode 100644 src/db/trk.ts create mode 100644 src/db/trkpt.test.ts create mode 100644 src/db/trkpt.ts create mode 100644 src/db/trkseg.test.ts create mode 100644 src/db/trkseg.ts create mode 100644 src/db/types.d.ts create mode 100644 src/db/wpt.test.ts create mode 100644 src/db/wpt.ts create mode 100644 src/lib/cache.ts create mode 100644 src/lib/docuri/index.js create mode 100644 src/lib/geo.ts create mode 100644 src/lib/gpx-parser-builder/LICENSE create mode 100644 src/lib/gpx-parser-builder/README.md create mode 100644 src/lib/gpx-parser-builder/package.json create mode 100644 src/lib/gpx-parser-builder/src/bounds.js create mode 100644 src/lib/gpx-parser-builder/src/copyright.js create mode 100644 src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts create mode 100644 src/lib/gpx-parser-builder/src/gpx.js create mode 100644 src/lib/gpx-parser-builder/src/link.js create mode 100644 src/lib/gpx-parser-builder/src/metadata.js create mode 100644 src/lib/gpx-parser-builder/src/person.js create mode 100644 src/lib/gpx-parser-builder/src/route.js create mode 100644 src/lib/gpx-parser-builder/src/track-segment.js create mode 100644 src/lib/gpx-parser-builder/src/track.js create mode 100644 src/lib/gpx-parser-builder/src/utils.js create mode 100644 src/lib/gpx-parser-builder/src/waypoint.js create mode 100644 src/lib/gpx.test.ts create mode 100644 src/lib/gpx.ts create mode 100644 src/lib/ids.test.ts create mode 100644 src/lib/ids.ts create mode 100644 src/lib/types.d.ts create mode 100644 src/missing-typedefs/docuri.d.ts create mode 100644 src/missing-typedefs/gpx-parser-builder.d.ts create mode 100644 src/workers/dispatcher-main.test.js create mode 100644 src/workers/dispatcher-main.ts create mode 100644 src/workers/dispatcher-worker.test.js create mode 100644 src/workers/dispatcher-worker.ts create mode 100644 src/workers/get-worker.ts diff --git a/package-lock.json b/package-lock.json index f585ac9..41c4075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,16 @@ "@capacitor/android": "^3.4.3", "@capacitor/core": "^3.4.3", "@capacitor/ios": "^3.4.3", + "@esbuild-plugins/node-globals-polyfill": "^0.1.1", + "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@solidjs/router": "^0.5.1", "@suid/icons-material": "^0.5.1", "@suid/material": "^0.8.0", + "lodash": "^4.17.21", "ol": "^7.1.0", + "pouchdb": "^7.3.1", + "pouchdb-browser": "^7.3.1", + "pouchdb-find": "^7.3.1", "solid-js": "^1.3.12" }, "devDependencies": { @@ -678,6 +684,37 @@ "@capacitor/core": "^3.9.0" } }, + "node_modules/@esbuild-plugins/node-globals-polyfill": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.1.1.tgz", + "integrity": "sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==", + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz", + "integrity": "sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "rollup-plugin-node-polyfills": "^0.2.1" + }, + "peerDependencies": { + "esbuild": "*" + } + }, + "node_modules/@esbuild-plugins/node-modules-polyfill/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.15.15", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.15.tgz", @@ -685,7 +722,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -701,7 +737,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1318,6 +1353,32 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -1385,6 +1446,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argsarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz", + "integrity": "sha512-u96dg2GcAKtpTrBdDoFIM7PjcBA+6rSP0OR94MOReNRyUECL6MtQt5XXmRr4qrftYaef9+l5hcpO5te7sML1Cg==" + }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", @@ -1482,7 +1548,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -1559,6 +1624,29 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -1568,6 +1656,11 @@ "node": "*" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1655,6 +1748,14 @@ "node": ">=8" } }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -1708,6 +1809,11 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1843,6 +1949,18 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "dependencies": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -1895,6 +2013,11 @@ "node": ">=12" } }, + "node_modules/double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" + }, "node_modules/earcut": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", @@ -1924,6 +2047,28 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "dependencies": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/end-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/end-stream/-/end-stream-0.1.0.tgz", + "integrity": "sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA==", + "dependencies": { + "write-stream": "~0.4.3" + } + }, "node_modules/entities": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", @@ -1945,6 +2090,17 @@ "node": ">=6" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/es-get-iterator": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", @@ -1968,7 +2124,6 @@ "version": "0.15.15", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.15.tgz", "integrity": "sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2008,7 +2163,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -2024,7 +2178,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -2040,7 +2193,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -2056,7 +2208,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -2072,7 +2223,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2088,7 +2238,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2104,7 +2253,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2120,7 +2268,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2136,7 +2283,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2152,7 +2298,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2168,7 +2313,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2184,7 +2328,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2200,7 +2343,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2216,7 +2358,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2232,7 +2373,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -2248,7 +2388,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -2264,7 +2403,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -2280,7 +2418,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2296,7 +2433,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2312,7 +2448,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2383,6 +2518,11 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2392,6 +2532,14 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", @@ -2423,6 +2571,17 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-cookie": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz", + "integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==", + "dependencies": { + "tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2736,6 +2895,11 @@ } ] }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -2748,8 +2912,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "3.0.1", @@ -3284,6 +3447,137 @@ "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==" }, + "node_modules/level": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "dependencies": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "engines": { + "node": ">=8.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "dependencies": { + "errno": "~0.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "dependencies": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "node_modules/level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "dependencies": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "dependencies": { + "xtend": "^4.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-write-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/level-write-stream/-/level-write-stream-1.0.0.tgz", + "integrity": "sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw==", + "dependencies": { + "end-stream": "~0.1.0" + } + }, + "node_modules/leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "hasInstallScript": true, + "dependencies": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "dependencies": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3312,8 +3606,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/loupe": { "version": "2.3.6", @@ -3335,6 +3628,11 @@ "node": ">=10" } }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" + }, "node_modules/lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", @@ -3344,6 +3642,14 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/mapbox-to-css-font": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.1.tgz", @@ -3470,6 +3776,11 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, "node_modules/native-run": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/native-run/-/native-run-1.7.1.tgz", @@ -3495,6 +3806,54 @@ "node": ">=12.13.0" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -3736,6 +4095,206 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pouchdb": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb/-/pouchdb-7.3.1.tgz", + "integrity": "sha512-oanSnM3SD9lPRuVRwEZWVbtWKYluw0q5phT5BXWi2b9Zqd5mJUPWKbKWJu03cDPM9wySmKKd7yfl9O9/eIQ5fg==", + "dependencies": { + "abort-controller": "3.0.0", + "argsarray": "0.0.1", + "buffer-from": "1.1.2", + "clone-buffer": "1.0.0", + "double-ended-queue": "2.1.0-0", + "fetch-cookie": "0.11.0", + "immediate": "3.3.0", + "inherits": "2.0.4", + "level": "6.0.1", + "level-codec": "9.0.2", + "level-write-stream": "1.0.0", + "leveldown": "5.6.0", + "levelup": "4.4.0", + "ltgt": "2.2.1", + "node-fetch": "2.6.7", + "readable-stream": "1.1.14", + "spark-md5": "3.0.2", + "through2": "3.0.2", + "uuid": "8.3.2", + "vuvuzela": "1.0.3" + } + }, + "node_modules/pouchdb-abstract-mapreduce": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-7.3.1.tgz", + "integrity": "sha512-0zKXVFBvrfc1KnN0ggrB762JDmZnUpePHywo9Bq3Jy+L1FnoG7fXM5luFfvv5/T0gEw+ZTIwoocZECMnESBI9w==", + "dependencies": { + "pouchdb-binary-utils": "7.3.1", + "pouchdb-collate": "7.3.1", + "pouchdb-collections": "7.3.1", + "pouchdb-errors": "7.3.1", + "pouchdb-fetch": "7.3.1", + "pouchdb-mapreduce-utils": "7.3.1", + "pouchdb-md5": "7.3.1", + "pouchdb-utils": "7.3.1" + } + }, + "node_modules/pouchdb-binary-utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-7.3.1.tgz", + "integrity": "sha512-crZJNfAEOnUoRk977Qtmk4cxEv6sNKllQ6vDDKgQrQLFjMUXma35EHzNyIJr1s76J77Q4sqKQAmxz9Y40yHGtw==", + "dependencies": { + "buffer-from": "1.1.2" + } + }, + "node_modules/pouchdb-browser": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-browser/-/pouchdb-browser-7.3.1.tgz", + "integrity": "sha512-qZ8awkXl/woBHvEVqNHjDtwPDA7A9v4ItHtX1y1eVpKel4mlYqnIJ8K6pRcFUZmVaHinJW8K3uS32eHC1q0yOA==", + "dependencies": { + "argsarray": "0.0.1", + "immediate": "3.3.0", + "inherits": "2.0.4", + "spark-md5": "3.0.2", + "uuid": "8.3.2", + "vuvuzela": "1.0.3" + } + }, + "node_modules/pouchdb-collate": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-7.3.1.tgz", + "integrity": "sha512-o4gyGqDMLMSNzf6EDTr3eHaH/JRMoqRhdc+eV+oA8u00nTBtr9wD+jypVe2LbgKLJ4NWqx2qVkXiTiQdUFtsLQ==" + }, + "node_modules/pouchdb-collections": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-7.3.1.tgz", + "integrity": "sha512-yUyDqR+OJmtwgExOSJegpBJXDLAEC84TWnbAYycyh+DZoA51Yw0+XVQF5Vh8Ii90/Ut2xo88fmrmp0t6kqom8w==" + }, + "node_modules/pouchdb-errors": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-7.3.1.tgz", + "integrity": "sha512-Zktz4gnXEUcZcty8FmyvtYUYsHskoST05m6H5/E2gg/0mCfEXq/XeyyLkZHaZmqD0ZPS9yNmASB1VaFWEKEaDw==", + "dependencies": { + "inherits": "2.0.4" + } + }, + "node_modules/pouchdb-fetch": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-7.3.1.tgz", + "integrity": "sha512-205xAtvdHRPQ4fp1h9+RmT9oQabo9gafuPmWsS9aEl3ER54WbY8Vaj1JHZGbU4KtMTYvW7H5088zLS7Nrusuag==", + "dependencies": { + "abort-controller": "3.0.0", + "fetch-cookie": "0.11.0", + "node-fetch": "2.6.7" + } + }, + "node_modules/pouchdb-find": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-find/-/pouchdb-find-7.3.1.tgz", + "integrity": "sha512-AeqUfAVY1c7IFaY36BRT0vIz9r4VTKq/YOWTmiqndOZUQ/pDGxyO2fNFal6NN3PyYww0JijlD377cPvhnrhJVA==", + "dependencies": { + "pouchdb-abstract-mapreduce": "7.3.1", + "pouchdb-collate": "7.3.1", + "pouchdb-errors": "7.3.1", + "pouchdb-fetch": "7.3.1", + "pouchdb-md5": "7.3.1", + "pouchdb-selector-core": "7.3.1", + "pouchdb-utils": "7.3.1" + } + }, + "node_modules/pouchdb-mapreduce-utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-7.3.1.tgz", + "integrity": "sha512-oUMcq82+4pTGQ6dtrhgORHOVHZSr6w/5tFIUGlv7RABIDvJarL4snMawADjlpiEwPdiQ/ESG8Fqt8cxqvqsIgg==", + "dependencies": { + "argsarray": "0.0.1", + "inherits": "2.0.4", + "pouchdb-collections": "7.3.1", + "pouchdb-utils": "7.3.1" + } + }, + "node_modules/pouchdb-md5": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-7.3.1.tgz", + "integrity": "sha512-aDV8ui/mprnL3xmt0gT/81DFtTtJiKyn+OxIAbwKPMfz/rDFdPYvF0BmDC9QxMMzGfkV+JJUjU6at0PPs2mRLg==", + "dependencies": { + "pouchdb-binary-utils": "7.3.1", + "spark-md5": "3.0.2" + } + }, + "node_modules/pouchdb-selector-core": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-7.3.1.tgz", + "integrity": "sha512-HBX+nNGXcaL9z0uNpwSMRq2GNZd3EZXW+fe9rJHS0hvJohjZL7aRJLoaXfEdHPRTNW+CpjM3Rny60eGekQdI/w==", + "dependencies": { + "pouchdb-collate": "7.3.1", + "pouchdb-utils": "7.3.1" + } + }, + "node_modules/pouchdb-utils": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-7.3.1.tgz", + "integrity": "sha512-R3hHBo1zTdTu/NFs3iqkcaQAPwhIH0gMIdfVKd5lbDYlmP26rCG5pdS+v7NuoSSFLJ4xxnaGV+Gjf4duYsJ8wQ==", + "dependencies": { + "argsarray": "0.0.1", + "clone-buffer": "1.0.0", + "immediate": "3.3.0", + "inherits": "2.0.4", + "pouchdb-collections": "7.3.1", + "pouchdb-errors": "7.3.1", + "pouchdb-md5": "7.3.1", + "uuid": "8.3.2" + } + }, + "node_modules/pouchdb/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/pouchdb/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/pouchdb/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/pouchdb/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/pouchdb/node_modules/through2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pouchdb/node_modules/through2/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -3798,17 +4357,20 @@ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -3816,8 +4378,7 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/quickselect": { "version": "2.0.0", @@ -3842,7 +4403,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3891,8 +4451,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.1", @@ -3934,6 +4493,33 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-inject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", + "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", + "dependencies": { + "estree-walker": "^0.6.1", + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-plugin-node-polyfills": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", + "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", + "dependencies": { + "rollup-plugin-inject": "^3.0.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -3952,7 +4538,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -4163,6 +4748,16 @@ "node": ">=0.10.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + }, "node_modules/split2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", @@ -4188,7 +4783,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -4348,7 +4942,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -4363,7 +4956,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, "engines": { "node": ">= 4.0.0" } @@ -4476,7 +5068,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -4485,8 +5076,15 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } }, "node_modules/vite": { "version": "3.2.4", @@ -4624,6 +5222,11 @@ } } }, + "node_modules/vuvuzela": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", + "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==" + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -4776,6 +5379,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/write-stream": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/write-stream/-/write-stream-0.4.3.tgz", + "integrity": "sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A==", + "dependencies": { + "readable-stream": "~0.0.2" + } + }, + "node_modules/write-stream/node_modules/readable-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-0.0.4.tgz", + "integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw==" + }, "node_modules/ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", @@ -4848,6 +5464,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index e2278ce..6279497 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,16 @@ "@capacitor/android": "^3.4.3", "@capacitor/core": "^3.4.3", "@capacitor/ios": "^3.4.3", + "@esbuild-plugins/node-globals-polyfill": "^0.1.1", + "@esbuild-plugins/node-modules-polyfill": "^0.1.4", "@solidjs/router": "^0.5.1", "@suid/icons-material": "^0.5.1", "@suid/material": "^0.8.0", + "lodash": "^4.17.21", "ol": "^7.1.0", + "pouchdb": "^7.3.1", + "pouchdb-browser": "^7.3.1", + "pouchdb-find": "^7.3.1", "solid-js": "^1.3.12" } } diff --git a/src/db/gpx.test.ts b/src/db/gpx.test.ts new file mode 100644 index 0000000..d74211d --- /dev/null +++ b/src/db/gpx.test.ts @@ -0,0 +1,86 @@ +import { initDb } from '.'; +import { existsGpx, putNewGpx } from './gpx'; +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The gpx module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new Gpx when required', async () => { + await putNewGpx({ gpx: 0 }); + expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/0000000000000000', + _rev: undefined, + doc: { + $: { + creator: 'dyomedea version 0.000002', + version: '1.1', + xmlns: 'http://www.topografix.com/GPX/1/1', + 'xmlns:dyo': 'http://xmlns.dyomedea.com/', + 'xmlns:gpxtpx': + 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1', + 'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3', + 'xmlns:wptx1': + 'http://www.garmin.com/xmlschemas/WaypointExtension/v1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': + 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd', + }, + extensions: undefined, + metadata: { + author: undefined, + bounds: undefined, + copyright: undefined, + desc: undefined, + extensions: undefined, + keywords: undefined, + link: undefined, + name: undefined, + time: '1970-01-01T00:00:00.000Z', + }, + rte: undefined, + trk: undefined, + wpt: undefined, + }, + type: 'gpx', + }); + }); + test('db.put() generates an id if needed', async () => { + const id = await putNewGpx(); + expect(id).toEqual({ gpx: 4320000000000000 }); + }); +}); + +describe('The gpx module with a real db', () => { + beforeEach(async () => { + await initDb({}); + globalThis.Date.now = () => 0; + }); + afterEach(async () => { + await db.destroy(); + db = undefined; + globalThis.Date.now = originalDateNow; + }); + + test("existsGpx returns false if the GPX doesn't exist", async () => { + const exists = await existsGpx({ gpx: 1 }); + expect(exists).toBeFalsy(); + }); + test('existsGpx returns false if the GPX exists', async () => { + const id = { gpx: 1 }; + await putNewGpx(id); + const exists = await existsGpx(id); + expect(exists).toBeTruthy(); + }); +}); diff --git a/src/db/gpx.ts b/src/db/gpx.ts new file mode 100644 index 0000000..cd08027 --- /dev/null +++ b/src/db/gpx.ts @@ -0,0 +1,269 @@ +import { PureComponent } from 'react'; +import { Point, Rectangle } from '../components/map/types'; +import { lat2tile, lon2tile, rectanglesIntersect } from '../lib/geo'; +import getUri, { intToGpxId } from '../lib/ids'; +import { get, getDocsByType, getFamily, put, putAll } from './lib'; + +const emptyGpx: Gpx = { + $: { + version: '1.1', + creator: 'dyomedea version 0.000002', + xmlns: 'http://www.topografix.com/GPX/1/1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': + 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd', + 'xmlns:gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3', + 'xmlns:wptx1': 'http://www.garmin.com/xmlschemas/WaypointExtension/v1', + 'xmlns:gpxtpx': 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1', + 'xmlns:dyo': 'http://xmlns.dyomedea.com/', + }, + metadata: { + name: undefined, + desc: undefined, + author: undefined, + copyright: undefined, + link: undefined, + time: undefined, + keywords: undefined, + bounds: undefined, + extensions: undefined, + }, + wpt: undefined, + rte: undefined, + trk: undefined, + extensions: undefined, +}; + +export const putNewGpx = async ( + id: IdGpx = { gpx: intToGpxId(Date.now()) } +) => { + const uri = getUri('gpx', id); + await put( + uri, + 'gpx', + (gpx) => { + (gpx.metadata ??= {}).time = new Date(Date.now()).toISOString(); + return gpx; + }, + emptyGpx + ); + return id; +}; + +export const existsGpx = async (id: IdGpx) => { + const uri = getUri('gpx', id); + try { + await get(uri); + return true; + } catch { + return false; + } +}; + +const prune = (id: any, object: any, docs: any[]) => { + if (typeof object === 'object') { + for (const key in object) { + if ( + key === 'wpt' || + key === 'rte' || + key === 'rtept' || + key === 'trk' || + key === 'trkseg' || + key === 'trkpt' + ) { + const subObjects = object[key]; + for (const index in subObjects) { + const subId = { ...id }; + subId[key] = index; + docs.push({ + _id: getUri(key, subId), + type: key, + doc: subObjects[index], + }); + prune(subId, subObjects[index], docs); + } + object[key] = undefined; + } else prune(id, object[key], docs); + } + } +}; + +const extensionsFromObject = ( + object: any, + extensions = { + viewport: { topLeft: {}, bottomRight: {} }, + bbox: { + minLon: undefined, + minLat: undefined, + maxLon: undefined, + maxLat: undefined, + }, + } +) => { + if (typeof object === 'object') { + if ('$' in object) { + const attributes = object.$; + if ('lat' in attributes) { + const lat = +attributes.lat; + if ( + extensions.bbox.minLat === undefined || + lat < extensions.bbox.minLat + ) { + extensions.bbox.minLat = lat; + } + if ( + extensions.bbox.maxLat === undefined || + lat > extensions.bbox.maxLat + ) { + extensions.bbox.maxLat = lat; + } + } + if ('lon' in attributes) { + const lon = +attributes.lon; + if ( + extensions.bbox.minLon === undefined || + lon < extensions.bbox.minLon + ) { + extensions.bbox.minLon = lon; + } + if ( + extensions.bbox.maxLon === undefined || + lon > extensions.bbox.maxLon + ) { + extensions.bbox.maxLon = lon; + } + } + } + for (const key in object) { + extensionsFromObject(object[key], extensions); + } + } + return extensions; +}; + +const extensionsFromGpx = (gpx: Gpx) => { + const extensions = { ...gpx.extensions, ...extensionsFromObject(gpx) }; + gpx.extensions = undefined; + if ( + extensions.bbox.maxLat !== undefined && + extensions.bbox.minLon !== undefined + ) { + extensions.viewport.topLeft = { + x: lon2tile(extensions.bbox.minLon, 0), + y: lat2tile(extensions.bbox.maxLat, 0), + }; + } + if ( + extensions.bbox.minLat !== undefined && + extensions.bbox.maxLon !== undefined + ) { + extensions.viewport.bottomRight = { + x: lon2tile(extensions.bbox.maxLon, 0), + y: lat2tile(extensions.bbox.minLat, 0), + }; + } + + return extensions; +}; + +export const pruneAndSaveImportedGpx = async (params: any) => { + const { id, gpx, extensions } = params; + let docs: any[] = [ + { _id: getUri('gpx', id), type: 'gpx', doc: gpx }, + { + _id: getUri('extensions', id), + type: 'extensions', + doc: { ...extensions, ...extensionsFromGpx(gpx) }, + }, + ]; + prune(id, gpx, docs); + console.log(JSON.stringify(docs)); + try { + const result = await putAll(docs); + console.log(JSON.stringify(result)); + } catch (err) { + console.error(`error: ${err}`); + } +}; + +export const getGpxesForViewport = async (params: any) => { + const { viewport, zoomLevel } = params; + const zoomedViewport: Rectangle = { + topLeft: { + x: viewport.topLeft.x / 2 ** zoomLevel, + y: viewport.topLeft.y / 2 ** zoomLevel, + }, + bottomRight: { + x: (viewport.bottomRight.x + 1) / 2 ** zoomLevel, + y: (viewport.bottomRight.y + 1) / 2 ** zoomLevel, + }, + }; + const allExtensions = await getDocsByType('extensions'); + console.log( + `getGpxesForViewport, allExtensions: ${JSON.stringify(allExtensions)}` + ); + return allExtensions + .filter((extensions: any) => { + return rectanglesIntersect(zoomedViewport, extensions.doc.viewport); + }) + .map((extensions: any) => + getUri('gpx', getUri('extensions', extensions._id)) + ); +}; + +const appendToArray = (target: any, key: string, value: any) => { + if (!(key in target)) { + target[key] = []; + } + target[key].push(value); +}; + +export const getFullGpx = async (params: any) => { + const { id } = params; + const docs = await getFamily(id, { include_docs: true }); + let target: any[]; + let gpx: Gpx | undefined = undefined; + docs.rows.forEach((row: any) => { + // level 0 + if (row.doc.type === 'gpx') { + target = [row.doc.doc]; + gpx = row.doc.doc; + } + //level 1 + if ( + row.doc.type === 'wpt' || + row.doc.type === 'rte' || + row.doc.type === 'trk' || + row.doc.type === 'extensions' + ) { + target.splice(1); + appendToArray(target.at(-1), row.doc.type, row.doc.doc); + target.push(row.doc.doc); + } + // level 2 + if (row.doc.type === 'rtept' || row.doc.type === 'trkseg') { + target.splice(2); + appendToArray(target.at(-1), row.doc.type, row.doc.doc); + target.push(row.doc.doc); + } + // level 3 + if (row.doc.type === 'trkpt') { + appendToArray(target.at(-1), row.doc.type, row.doc.doc); + } + }); + return gpx; +}; + +export const getGpx = async (params: any) => { + const { id } = params; + const docs = await getFamily(id, { include_docs: true }); + console.log(`getGpx, uri: ${id} docs: ${JSON.stringify(docs)}`); + return docs.rows.filter( + (row: any) => + row.doc.type === 'gpx' || + row.doc.type === 'wpt' || + row.doc.type === 'rte' || + row.doc.type === 'trk' || + row.doc.type === 'extensions' + ); +}; diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..49536bf --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,117 @@ +import _ from 'lodash'; +import PouchDB from 'pouchdb'; +import PouchDBFind from 'pouchdb-find'; +import uri from '../lib/ids'; + +PouchDB.plugin(PouchDBFind); + +const dbDefinitionId = uri('dbdef', {}); + +const currentDbDefinition = { + _id: dbDefinitionId, + type: dbDefinitionId, + def: { version: '0.000001' }, +}; + +declare global { + var db: any; + var dbReady: boolean; +} + +export const initDb = async (params: any) => { + if (globalThis.db === undefined) { + globalThis.db = new PouchDB('dyomedea', { + auto_compaction: true, + revs_limit: 10, + }); + } + const db = globalThis.db; + var previousDbDefinition = { + _id: dbDefinitionId, + type: dbDefinitionId, + def: { version: '0' }, + }; + try { + previousDbDefinition = await db.get(dbDefinitionId); + } catch (error: any) { + if (error.status !== 404) { + console.log( + `Unexpected error fetching db definition: ${JSON.stringify(error)}` + ); + return; + } + } + + if (previousDbDefinition.def.version < currentDbDefinition.def.version) { + previousDbDefinition.def = currentDbDefinition.def; + db.put(previousDbDefinition); + // TODO: support migrations + } + await await db.compact(); + await db.viewCleanup(); + + // WARNING: defs must use the canonical form and be identical to what will be returned by db.getIndexes + const requiredIndexes: any = [ + { + name: 'type', + def: { + fields: [{ type: 'asc' }], + }, + }, + ]; + + const existingIndexes = (await db.getIndexes()).indexes; + + const pruneIndex = ({ name, def }: any) => ({ name, def }); + + const isSameIndex = (idx1: any, idx2: any) => { + return _.isEqual(pruneIndex(idx1), pruneIndex(idx2)); + }; + + const findIndex = (targetIndexes: any, index: any) => + targetIndexes.find((targetIndex: any) => { + return isSameIndex(targetIndex, index); + }); + + for (var index of existingIndexes) { + if (index.type === 'json') { + // Non system indexes + // console.log(`Checking existing index :${JSON.stringify(index)}`); + if (!findIndex(requiredIndexes, index)) { + // console.log(`db.deleteIndex(${JSON.stringify(index)})`); + await db.deleteIndex(index); + } + } + } + + for (index of requiredIndexes) { + if (!findIndex(existingIndexes, index)) { + // console.log(`db.createIndex(${JSON.stringify(index)})`); + await db.createIndex({ name: index.name, ...index.def }); + } + } + + globalThis.dbReady = true; + + /* const indexes = await db.getIndexes(); + console.log(`indexes: ${JSON.stringify(indexes)}`); + + const explain1 = await db.explain({ + selector: { + type: 'trkpt', + gpx: 'xxxx', + }, + // sort: ['trkpt.time'], + // use_index: 'type-trkpt-gpx-time', + }); + console.log(`explain1: ${JSON.stringify(explain1)}`); + const explain2 = await db.explain({ + selector: { + type: 'gpx', + }, + // sort: ['trkpt.time'], + // use_index: 'type-trkpt-gpx-time', + }); + console.log(`explain2: ${JSON.stringify(explain2)}`); + */ +}; diff --git a/src/db/lib.test.ts b/src/db/lib.test.ts new file mode 100644 index 0000000..2d8d58f --- /dev/null +++ b/src/db/lib.test.ts @@ -0,0 +1,299 @@ +import { initDb } from '.'; +import uri from '../lib/ids'; +import { getDocsByType, getFamily } from './lib'; +import { putNewRte } from './rte'; +import { putNewRtept } from './rtept'; +import { putNewTrk } from './trk'; +import { putNewTrkpt } from './trkpt'; +import { putNewTrkseg } from './trkseg'; +import { putNewWpt } from './wpt'; + +declare global { + var db: any; +} + +const originalDateNow = globalThis.Date.now; + +describe('getFamily', () => { + beforeEach(async () => { + await initDb({}); + globalThis.Date.now = () => 0; + }); + afterEach(async () => { + await db.destroy(); + db = undefined; + globalThis.Date.now = originalDateNow; + }); + test('returns two rows after a gpx and a track have been inserted.', async () => { + await putNewTrk(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000", + "key": "gpx/4320000000000000/3trk/000000", + "value": Object { + "rev": "1-4c114f3ae0073151e4082ff1d220c2a4", + }, + }, + ], + "total_rows": 4, +} +`); + }); + test('also returns the docs if required.', async () => { + await putNewTrk(); + const allDocs: any = await getFamily('gpx/', { + include_docs: true, + }); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "doc": Object { + "_id": "gpx/4320000000000000", + "_rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + "doc": Object { + "$": Object { + "creator": "dyomedea version 0.000002", + "version": "1.1", + "xmlns": "http://www.topografix.com/GPX/1/1", + "xmlns:dyo": "http://xmlns.dyomedea.com/", + "xmlns:gpxtpx": "http://www.garmin.com/xmlschemas/TrackPointExtension/v1", + "xmlns:gpxx": "http://www.garmin.com/xmlschemas/GpxExtensions/v3", + "xmlns:wptx1": "http://www.garmin.com/xmlschemas/WaypointExtension/v1", + "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "xsi:schemaLocation": "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd", + }, + "metadata": Object { + "time": "1970-01-01T00:00:00.000Z", + }, + }, + "type": "gpx", + }, + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "doc": Object { + "_id": "gpx/4320000000000000/3trk/000000", + "_rev": "1-4c114f3ae0073151e4082ff1d220c2a4", + "doc": Object { + "number": 0, + }, + "type": "trk", + }, + "id": "gpx/4320000000000000/3trk/000000", + "key": "gpx/4320000000000000/3trk/000000", + "value": Object { + "rev": "1-4c114f3ae0073151e4082ff1d220c2a4", + }, + }, + ], + "total_rows": 4, +} +`); + }); + test('returns three rows after a gpx and a track segment have been inserted.', async () => { + await putNewTrkseg(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000", + "key": "gpx/4320000000000000/3trk/000000", + "value": Object { + "rev": "1-4c114f3ae0073151e4082ff1d220c2a4", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000/000000", + "key": "gpx/4320000000000000/3trk/000000/000000", + "value": Object { + "rev": "1-68d7de0569de570229ea9f9e1a0b13cb", + }, + }, + ], + "total_rows": 5, +} +`); + }); + test('returns four rows after a gpx and a track point have been inserted.', async () => { + await putNewTrkpt(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000", + "key": "gpx/4320000000000000/3trk/000000", + "value": Object { + "rev": "1-4c114f3ae0073151e4082ff1d220c2a4", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000/000000", + "key": "gpx/4320000000000000/3trk/000000/000000", + "value": Object { + "rev": "1-68d7de0569de570229ea9f9e1a0b13cb", + }, + }, + Object { + "id": "gpx/4320000000000000/3trk/000000/000000/000000", + "key": "gpx/4320000000000000/3trk/000000/000000/000000", + "value": Object { + "rev": "1-7d917d1f3505fe0e3092161694904b53", + }, + }, + ], + "total_rows": 6, +} +`); + }); + + test('returns two rows after a gpx and a waypoint have been inserted.', async () => { + await putNewWpt(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/1wpt/000000", + "key": "gpx/4320000000000000/1wpt/000000", + "value": Object { + "rev": "1-c6793365fd0dd56236ab8734a41c7ae7", + }, + }, + ], + "total_rows": 4, +} +`); + }); + + test('returns two rows after a gpx and a route have been inserted.', async () => { + await putNewRte(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/2rte/000000", + "key": "gpx/4320000000000000/2rte/000000", + "value": Object { + "rev": "1-2ca14f512a9c83f5a239389e580befce", + }, + }, + ], + "total_rows": 4, +} +`); + }); + + test('returns three rows after a gpx and a route point have been inserted.', async () => { + await putNewRtept(); + const allDocs: any = await getFamily('gpx/'); + expect(allDocs).toMatchInlineSnapshot(` +Object { + "offset": 0, + "rows": Array [ + Object { + "id": "gpx/4320000000000000", + "key": "gpx/4320000000000000", + "value": Object { + "rev": "1-49baa096ec0c89962f2cafd3ff50b80b", + }, + }, + Object { + "id": "gpx/4320000000000000/2rte/000000", + "key": "gpx/4320000000000000/2rte/000000", + "value": Object { + "rev": "1-2ca14f512a9c83f5a239389e580befce", + }, + }, + Object { + "id": "gpx/4320000000000000/2rte/000000/000000", + "key": "gpx/4320000000000000/2rte/000000/000000", + "value": Object { + "rev": "1-0f4064d20f6bfac3888a7758851fbac5", + }, + }, + ], + "total_rows": 5, +} +`); + }); +}); + +describe('getDocsByType', () => { + beforeEach(async () => { + await initDb({}); + globalThis.Date.now = () => 0; + }); + afterEach(async () => { + await db.destroy(); + db = undefined; + globalThis.Date.now = originalDateNow; + }); + test('gets the rte amongst other docs', async () => { + await putNewRtept(); + const rtes = await getDocsByType('rte'); + expect(rtes).toMatchInlineSnapshot(` +Array [ + Object { + "_id": "gpx/4320000000000000/2rte/000000", + "_rev": "1-2ca14f512a9c83f5a239389e580befce", + "doc": Object { + "number": 0, + }, + "type": "rte", + }, +] +`); + }); +}); diff --git a/src/db/lib.ts b/src/db/lib.ts new file mode 100644 index 0000000..0b98445 --- /dev/null +++ b/src/db/lib.ts @@ -0,0 +1,52 @@ +import { cloneDeep } from 'lodash'; + +declare global { + var db: any; +} + +export const put = async ( + _id: string, + type: string, + update: (doc: any) => any, + defaultDoc: any +) => { + var current; + try { + current = await db.get(_id); + } catch { + current = { _rev: undefined, doc: cloneDeep(defaultDoc) }; + } + try { + db.put({ _id, _rev: current._rev, type, doc: update(current.doc) }); + } catch (error: any) { + if (error.name === 'conflict') { + await put(_id, type, update, defaultDoc); + } else { + console.error( + `put(${_id}, ${JSON.stringify( + update(current.doc) + )}), error: ${JSON.stringify(error)}` + ); + } + } +}; + +export const getFamily = async (key: string, options: any = {}) => { + return await db.allDocs({ + startkey: key, + endkey: key + '\ufff0', + ...options, + }); +}; + +export const get = async (id: string) => { + await db.get(id); +}; + +export const putAll = async (docs: any[]) => { + return await db.bulkDocs(docs); +}; + +export const getDocsByType = async (type: string) => { + return (await db.find({ selector: { type: type } })).docs; +}; diff --git a/src/db/rte.test.ts b/src/db/rte.test.ts new file mode 100644 index 0000000..473a6f9 --- /dev/null +++ b/src/db/rte.test.ts @@ -0,0 +1,47 @@ +import { putNewRte } from './rte'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The rte module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new rte when required', async () => { + putNewRte({ gpx: 4320000000000000, rte: 25 }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/4320000000000000/2rte/000025', + _rev: undefined, + doc: { + cmt: undefined, + desc: undefined, + extensions: undefined, + link: undefined, + name: undefined, + number: 0, + rtept: undefined, + src: undefined, + type: undefined, + }, + type: 'rte', + }); + }); + test('db.put() generates an id for the trk if needed', async () => { + const id = await putNewRte({ gpx: 0 }); + expect(id).toEqual({ gpx: 0, rte: 0 }); + }); + test('db.put() generates ids for both gpx and trk if needed', async () => { + const id = await putNewRte(); + expect(id).toEqual({ gpx: 4320000000000000, rte: 0 }); + }); +}); diff --git a/src/db/rte.ts b/src/db/rte.ts new file mode 100644 index 0000000..8febbc9 --- /dev/null +++ b/src/db/rte.ts @@ -0,0 +1,33 @@ +import getUri from '../lib/ids'; +import { putNewGpx } from './gpx'; +import { put } from './lib'; + +export const emptyRte: Rte = { + name: undefined, + cmt: undefined, + desc: undefined, + src: undefined, + link: undefined, + number: 0, + type: undefined, + extensions: undefined, + rtept: undefined, +}; + +export const putNewRte = async (id?: IdRte | IdGpx) => { + let finalId = { ...id }; + if (!('rte' in finalId)) { + const gpxId = await putNewGpx(id); + finalId = { ...gpxId, rte: 0 }; + } + const uri = getUri('rte', finalId); + await put( + uri, + 'rte', + (rte) => { + return rte; + }, + emptyRte + ); + return finalId as IdRte; +}; diff --git a/src/db/rtept.test.ts b/src/db/rtept.test.ts new file mode 100644 index 0000000..4e993fb --- /dev/null +++ b/src/db/rtept.test.ts @@ -0,0 +1,58 @@ +import { putNewRtept } from './rtept'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The rtept module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new rtept when required', async () => { + putNewRtept({ gpx: 0, rte: 0, rtept: 0 }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/0000000000000000/2rte/000000/000000', + _rev: undefined, + doc: { + $: { lat: 0, lon: 0 }, + ageofdgpsdata: undefined, + cmt: undefined, + desc: undefined, + dgpsid: undefined, + ele: undefined, + extensions: undefined, + fix: undefined, + geoidheight: undefined, + hdop: undefined, + link: undefined, + magvar: undefined, + name: undefined, + pdop: undefined, + sat: undefined, + src: undefined, + sym: undefined, + time: undefined, + type: undefined, + vdop: undefined, + }, + type: 'rtept', + }); + }); + test('db.put() generates an id for the rtept if needed', async () => { + const id = await putNewRtept({ gpx: 0 }); + expect(id).toEqual({ gpx: 0, rte: 0, rtept: 0 }); + }); + test('db.put() generates ids for both gpx and rte if needed', async () => { + const id = await putNewRtept(); + expect(id).toEqual({ gpx: 4320000000000000, rte: 0, rtept: 0 }); + }); +}); diff --git a/src/db/rtept.ts b/src/db/rtept.ts new file mode 100644 index 0000000..cb4a8c1 --- /dev/null +++ b/src/db/rtept.ts @@ -0,0 +1,44 @@ +import getUri from '../lib/ids'; +import { put } from './lib'; +import { putNewRte } from './rte'; + +export const emptyRtept: Wpt = { + $: { lat: 0, lon: 0 }, + ele: undefined, + time: undefined, + magvar: undefined, + geoidheight: undefined, + name: undefined, + cmt: undefined, + desc: undefined, + src: undefined, + link: undefined, + sym: undefined, + type: undefined, + fix: undefined, + sat: undefined, + hdop: undefined, + vdop: undefined, + pdop: undefined, + ageofdgpsdata: undefined, + dgpsid: undefined, + extensions: undefined, +}; + +export const putNewRtept = async (id?: IdGpx | IdRte | IdRtept) => { + let finalId = { ...id }; + if (!('rtept' in finalId)) { + const rteId = await putNewRte(id); + finalId = { ...rteId, rtept: 0 }; + } + const uri = getUri('rtept', finalId); + await put( + uri, + 'rtept', + (rtept) => { + return rtept; + }, + emptyRtept + ); + return finalId as IdRtept; +}; diff --git a/src/db/trk.test.ts b/src/db/trk.test.ts new file mode 100644 index 0000000..02cdc61 --- /dev/null +++ b/src/db/trk.test.ts @@ -0,0 +1,47 @@ +import { putNewTrk } from './trk'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The trk module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new trk when required', async () => { + putNewTrk({ gpx: 1, trk: 2 }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/0000000000000001/3trk/000002', + _rev: undefined, + doc: { + cmt: undefined, + desc: undefined, + extensions: undefined, + link: undefined, + name: undefined, + number: 0, + src: undefined, + trkseg: undefined, + type: undefined, + }, + type: 'trk', + }); + }); + test('db.put() generates an id for the trk if needed', async () => { + const id = await putNewTrk({ gpx: 2 }); + expect(id).toEqual({ gpx: 2, trk: 0}); + }); + test('db.put() generates ids for both gpx and trk if needed', async () => { + const id = await putNewTrk(); + expect(id).toEqual({ gpx: 4320000000000000, trk: 0}); + }); +}); diff --git a/src/db/trk.ts b/src/db/trk.ts new file mode 100644 index 0000000..70f8f5c --- /dev/null +++ b/src/db/trk.ts @@ -0,0 +1,42 @@ +import getUri from '../lib/ids'; +import { putNewGpx } from './gpx'; +import { getFamily, put } from './lib'; + +export const emptyTrk: Trk = { + name: undefined, + cmt: undefined, + desc: undefined, + src: undefined, + link: undefined, + number: 0, + type: undefined, + extensions: undefined, + trkseg: undefined, +}; + +export const putNewTrk = async (id?: IdTrk | IdGpx) => { + let finalId = { ...id }; + if (!('trk' in finalId)) { + const gpxId = await putNewGpx(id); + finalId = { ...gpxId, trk: 0 }; + } + const uri = getUri('trk', finalId); + await put( + uri, + 'trk', + (trk) => { + return trk; + }, + emptyTrk + ); + return finalId as IdTrk; +}; + +export const getTrk = async (params: any) => { + const { id } = params; + const docs = await getFamily(id, { include_docs: true }); + console.log(`getTrk, uri: ${id} docs: ${JSON.stringify(docs)}`); + return docs.rows.filter( + (row: any) => row.doc.type === 'trk' || row.doc.type === 'trkseg' + ); +}; diff --git a/src/db/trkpt.test.ts b/src/db/trkpt.test.ts new file mode 100644 index 0000000..b4cfb0f --- /dev/null +++ b/src/db/trkpt.test.ts @@ -0,0 +1,79 @@ +import { putNewTrkpt } from './trkpt'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The trkpt module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new trkpt when required', async () => { + putNewTrkpt({ + gpx: 1, + trk: 2, + trkseg: 3, + trkpt: 4, + }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/0000000000000001/3trk/000002/000003/000004', + _rev: undefined, + doc: { + $: { lat: 0, lon: 0 }, + ageofdgpsdata: undefined, + cmt: undefined, + desc: undefined, + dgpsid: undefined, + ele: undefined, + extensions: { + 'dyo:accuracy': undefined, + 'dyo:batterylevel': undefined, + 'dyo:course': undefined, + 'dyo:speed': undefined, + 'dyo:useragent': undefined, + }, + fix: undefined, + geoidheight: undefined, + hdop: undefined, + link: undefined, + magvar: undefined, + name: undefined, + pdop: undefined, + sat: undefined, + src: undefined, + sym: undefined, + time: undefined, + type: undefined, + vdop: undefined, + }, + type: 'trkpt', + }); + }); + test('db.put() generates an id for the trk if needed', async () => { + const id = await putNewTrkpt({ gpx: 5 }); + expect(id).toEqual({ + gpx: 5, + trk: 0, + trkseg: 0, + trkpt: 0, + }); + }); + test('db.put() generates ids for both gpx and trk if needed', async () => { + const id = await putNewTrkpt(); + expect(id).toEqual({ + gpx: 4320000000000000, + trk: 0, + trkseg: 0, + trkpt: 0, + }); + }); +}); diff --git a/src/db/trkpt.ts b/src/db/trkpt.ts new file mode 100644 index 0000000..484be6a --- /dev/null +++ b/src/db/trkpt.ts @@ -0,0 +1,50 @@ +import getUri from '../lib/ids'; +import { put } from './lib'; +import { putNewTrkseg } from './trkseg'; + +const emptyTrkpt: Wpt = { + $: { lat: 0, lon: 0 }, + ele: undefined, + time: undefined, + magvar: undefined, + geoidheight: undefined, + name: undefined, + cmt: undefined, + desc: undefined, + src: undefined, + link: undefined, + sym: undefined, + type: undefined, + fix: undefined, + sat: undefined, + hdop: undefined, + vdop: undefined, + pdop: undefined, + ageofdgpsdata: undefined, + dgpsid: undefined, + extensions: { + 'dyo:speed': undefined, + 'dyo:course': undefined, + 'dyo:accuracy': undefined, + 'dyo:batterylevel': undefined, + 'dyo:useragent': undefined, + }, +}; + +export const putNewTrkpt = async (id?: IdTrk | IdGpx | IdTrkseg | IdTrkpt) => { + let finalId = { ...id }; + if (!('trkpt' in finalId)) { + const trksegId = await putNewTrkseg(id); + finalId = { ...trksegId, trkpt: 0 }; + } + const uri = getUri('trkpt', finalId); + await put( + uri, + 'trkpt', + (trkpt) => { + return trkpt; + }, + emptyTrkpt + ); + return finalId; +}; diff --git a/src/db/trkseg.test.ts b/src/db/trkseg.test.ts new file mode 100644 index 0000000..ca82725 --- /dev/null +++ b/src/db/trkseg.test.ts @@ -0,0 +1,40 @@ +import { putNewTrkseg } from './trkseg'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The trkseg module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new trk when required', async () => { + putNewTrkseg({ gpx: 1234567890123456, trk: 123456, trkseg: 5 }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/1234567890123456/3trk/123456/000005', + _rev: undefined, + doc: { + trkpt: undefined, + extensions: undefined, + }, + type: 'trkseg', + }); + }); + test('db.put() generates an id for the trk if needed', async () => { + const id = await putNewTrkseg({ gpx: 1 }); + expect(id).toEqual({ gpx: 1, trk: 0, trkseg: 0 }); + }); + test('db.put() generates ids for both gpx and trk if needed', async () => { + const id = await putNewTrkseg(); + expect(id).toEqual({ gpx: 4320000000000000, trk: 0, trkseg: 0 }); + }); +}); diff --git a/src/db/trkseg.ts b/src/db/trkseg.ts new file mode 100644 index 0000000..b6ad518 --- /dev/null +++ b/src/db/trkseg.ts @@ -0,0 +1,33 @@ +import getUri from '../lib/ids'; +import { getFamily, put } from './lib'; +import { putNewTrk } from './trk'; + +const emptyTrkseg: Trkseg = { + trkpt: undefined, + extensions: undefined, +}; + +export const putNewTrkseg = async (id?: IdTrk | IdGpx | IdTrkseg) => { + let finalId = { ...id }; + if (!('trkseg' in finalId)) { + const trkId = await putNewTrk(id); + finalId = { ...trkId, trkseg: 0 }; + } + const uri = getUri('trkseg', finalId); + await put( + uri, + 'trkseg', + (trkseg) => { + return trkseg; + }, + emptyTrkseg + ); + return finalId as IdTrkseg; +}; + +export const getTrkseg = async (params: any) => { + const { id } = params; + const docs = await getFamily(id, { include_docs: true }); + console.log(`getTrkseg, uri: ${id} docs: ${JSON.stringify(docs)}`); + return docs.rows; +}; diff --git a/src/db/types.d.ts b/src/db/types.d.ts new file mode 100644 index 0000000..c730257 --- /dev/null +++ b/src/db/types.d.ts @@ -0,0 +1,119 @@ +interface Gpx { + $: Gpx_; + metadata?: Metadata; + wpt?: Wpt[]; + rte?: Rte[]; + trk?: Trk[]; + extensions?: Extensions; +} + +interface Gpx_ { + version: '1.1'; + creator: string; + xmlns: 'http://www.topografix.com/GPX/1/1'; + 'xmlns:xsi'?: 'http://www.w3.org/2001/XMLSchema-instance'; + 'xsi:schemaLocation'?: string; + 'xmlns:gpxx'?: string; + 'xmlns:wptx1'?: string; + 'xmlns:gpxtpx'?: string; + 'xmlns:dyo'?: 'http://xmlns.dyomedea.com/'; +} + +interface Metadata { + name?: string; + desc?: string; + author?: string; + copyright?: string; + link?: Link[]; + time?: string; + keywords?: string; + bounds?: Bounds; + extensions?: Extensions; +} + +interface Bounds { + $: Bounds_; +} + +interface Bounds_ { + minlat: number; + minlon: number; + maxlat: number; + maxlon: number; +} + +interface Extensions { + 'dyo:speed'?: number; + 'dyo:course'?: number; + 'dyo:accuracy'?: number; + 'dyo:batterylevel'?: number; + 'dyo:useragent'?: string; +} + +interface Trk { + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link[]; + number?: number; + type?: string; + extensions?: Extensions; + trkseg?: Trkseg[]; +} + +interface Link { + $: Link_; + text?: string; + type?: string; +} + +interface Link_ { + href: string; +} + +interface Trkseg { + trkpt?: Wpt[]; + extensions?: Extensions; +} + +interface Wpt { + $: Wpt_; + ele?: number; + time?: string; + magvar?: number; + geoidheight?: number; + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link; + sym?: string; + type?: string; + fix?: string; + sat?: number; + hdop?: number; + vdop?: number; + pdop?: number; + ageofdgpsdata?: number; + dgpsid?: number; + extensions?: Extensions; +} + +interface Wpt_ { + lat: number; + lon: number; +} + +interface Rte { + name?: string; + cmt?: string; + desc?: string; + src?: string; + link?: Link[]; + number?: number; + type?: string; + extensions?: Extensions; + rtept?: Wpt[]; +} + diff --git a/src/db/wpt.test.ts b/src/db/wpt.test.ts new file mode 100644 index 0000000..998c373 --- /dev/null +++ b/src/db/wpt.test.ts @@ -0,0 +1,58 @@ +import { putNewWpt } from './wpt'; + +declare global { + var db: any; + var dbReady: boolean; +} + +const originalDb = globalThis.db; +const originalDateNow = globalThis.Date.now; + +describe('The wpt module', () => { + beforeEach(() => { + globalThis.db = { put: jest.fn() }; + globalThis.Date.now = () => 0; + }); + afterEach(() => { + globalThis.db = originalDb; + globalThis.Date.now = originalDateNow; + }); + test('db.put() a new wpt when required', async () => { + putNewWpt({ gpx: 1, wpt: 2 }); + await expect(globalThis.db.put).toBeCalledWith({ + _id: 'gpx/0000000000000001/1wpt/000002', + _rev: undefined, + doc: { + $: { lat: 0, lon: 0 }, + ageofdgpsdata: undefined, + cmt: undefined, + desc: undefined, + dgpsid: undefined, + ele: undefined, + extensions: undefined, + fix: undefined, + geoidheight: undefined, + hdop: undefined, + link: undefined, + magvar: undefined, + name: undefined, + pdop: undefined, + sat: undefined, + src: undefined, + sym: undefined, + time: undefined, + type: undefined, + vdop: undefined, + }, + type: 'wpt', + }); + }); + test('db.put() generates an id for the wpt if needed', async () => { + const id = await putNewWpt({ gpx: 1 }); + expect(id).toEqual({ gpx: 1, wpt: 0 }); + }); + test('db.put() generates ids for both gpx and trk if needed', async () => { + const id = await putNewWpt(); + expect(id).toEqual({ gpx: 4320000000000000, wpt: 0 }); + }); +}); diff --git a/src/db/wpt.ts b/src/db/wpt.ts new file mode 100644 index 0000000..fd8c8e0 --- /dev/null +++ b/src/db/wpt.ts @@ -0,0 +1,44 @@ +import getUri from '../lib/ids'; +import { putNewGpx } from './gpx'; +import { put } from './lib'; + +export const emptyWpt: Wpt = { + $: { lat: 0, lon: 0 }, + ele: undefined, + time: undefined, + magvar: undefined, + geoidheight: undefined, + name: undefined, + cmt: undefined, + desc: undefined, + src: undefined, + link: undefined, + sym: undefined, + type: undefined, + fix: undefined, + sat: undefined, + hdop: undefined, + vdop: undefined, + pdop: undefined, + ageofdgpsdata: undefined, + dgpsid: undefined, + extensions: undefined, +}; + +export const putNewWpt = async (id?: IdGpx | IdWpt) => { + let finalId = { ...id }; + if (!('wpt' in finalId)) { + const gpxId = await putNewGpx(id); + finalId = { ...gpxId, wpt: 0 }; + } + const uri = getUri('wpt', finalId); + await put( + uri, + 'wpt', + (wpt) => { + return wpt; + }, + emptyWpt + ); + return finalId as IdWpt; +}; diff --git a/src/index.tsx b/src/index.tsx index e77a4e9..4b72bc1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,10 +2,42 @@ import { render } from 'solid-js/web'; import { Router, hashIntegration } from '@solidjs/router'; import App from './App'; -//source={hashIntegration()} +import dispatch from './workers/dispatcher-main'; + +/* // See https://stackoverflow.com/questions/71538643/property-wakelock-does-not-exist-on-type-navigator +const requestWakeLock = async () => { + const anyNav: any = navigator; + if ('wakeLock' in navigator) { + try { + const wakeLock = await anyNav['wakeLock'].request('screen'); + } catch (err: any) { + // The wake lock request fails - usually system-related, such as low battery. + console.log(`Wake lock request failed: ${err.name}, ${err.message}`); + } + } else { + console.log('No wake lock support here...'); + } +}; + +const handleVisibilityChange = () => { + if (document.hidden) { + console.log('Application hidden'); + } else { + console.log('Application visible'); + requestWakeLock(); + } +}; + +// See https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API +document.addEventListener('visibilitychange', handleVisibilityChange, false); +requestWakeLock(); */ + +// Init the database +dispatch({ action: 'initDb' }); + render( () => ( - + ), diff --git a/src/lib/cache.ts b/src/lib/cache.ts new file mode 100644 index 0000000..14e3dc6 --- /dev/null +++ b/src/lib/cache.ts @@ -0,0 +1,61 @@ +/** + * @hidden + */ +export const thisIsAModule = true; + +/** + * + */ + +declare global { + var _allCaches: any; +} + +globalThis._allCaches = new Map(); + +const cache = { + set: (params: any) => { + const { cacheId, key, value } = params; + if (!_allCaches.has(cacheId)) { + _allCaches.set(cacheId, new Map()); + } + const k = _allCaches.get(cacheId); + k.set(key, value); + }, + get: (params: any) => { + const { cacheId, key } = params; + if (!_allCaches.has(cacheId)) { + return null; + } + const k = _allCaches.get(cacheId); + if (!k.has(key)) { + return null; + } + const value = k.get(key); + k.delete(key); + k.set(key, value); + return value; + }, + delete: (params: any) => { + const { cacheId, key } = params; + if (!_allCaches.has(cacheId)) { + return null; + } + const k = _allCaches.get(cacheId); + if (!k.has(key)) { + return null; + } + const value = k.get(key); + k.delete(key); + return value; + }, + map: (params: any) => { + const { cacheId } = params; + if (!_allCaches.has(cacheId)) { + return null; + } + return _allCaches.get(cacheId); + }, +}; + +export default cache; diff --git a/src/lib/docuri/index.js b/src/lib/docuri/index.js new file mode 100644 index 0000000..c236b41 --- /dev/null +++ b/src/lib/docuri/index.js @@ -0,0 +1,133 @@ +/* + * DocURI: Rich document ids for CouchDB. + * + * Copyright (c) 2014 null2 GmbH Berlin + * Licensed under the MIT license. + */ + +// type/id/subtype/index/version + +// var docuri = (module.exports = exports = {}); + +// Cached regular expressions for matching named param parts and splatted parts +// of route strings. +// http://backbonejs.org/docs/backbone.html#section-158 +var optionalParam = /\((.*?)\)/g; +var namedParam = /(\(\?)?:\w+/g; +var splatParam = /\*\w+/g; +var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; +var paramKeys = /[*:]\w+/g; + +// Convert a route string into a regular expression, +// with named regular expressions for named arguments. +// http://backbonejs.org/docs/backbone.html#section-165 +function routeToRegExp(src) { + var keys = [], + match; + + while ((match = paramKeys.exec(src)) !== null) { + keys.push(match[0]); + } + + var route = src + .replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function (match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + + keys = keys.reduce(function (memo, key) { + var value = '\\' + key; + + memo[key] = new RegExp(value + '(\\/|\\)|\\(|$)'); + + return memo; + }, {}); + + return { + src: src, + exp: new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'), + keys: keys, + }; +} + +// Given a route and a DocURI return an object of extracted parameters. +// Unmatched DocURIs will be treated as false. +// http://backbonejs.org/docs/backbone.html#section-166 +function extractParameters(route, fragment, coding) { + var params = route.exp.exec(fragment); + + if (!params) { + return false; + } + + params = params.slice(1); + + return Object.keys(route.keys).reduce(function (memo, key, i) { + var param = params[i]; + + if (param) { + var k = key.substr(1); + var decoder = (coding[k] ?? { decoder: decodeURIComponent }).decoder; + + if (key[0] === '*') { + param = param.split('/').map(decoder); + } else { + param = decoder(param); + } + + memo[key.substr(1)] = param; + } + + return memo; + }, {}); +} + +// Insert named parameters from object. +function insertParameters(route, obj, coding) { + var str = route.src; + + Object.keys(route.keys).forEach(function (key) { + var k = key.substr(1); + var value = obj[k] !== undefined ? obj[k] : ''; + + var encoder = (coding[k] ?? { encoder: encodeURIComponent }).encoder; + + if (Array.isArray(value)) { + value = value.map(encoder).join('/'); + } else { + value = encoder(value); + } + + str = str.replace(route.keys[key], value + '$1'); + }); + + // massage optional parameter + return str.replace(/\(\/\)/g, '').replace(/[)(]/g, ''); +} + +export const route = (route, coding = {}) => { + route = routeToRegExp(route); + + return function (source, target) { + source = source || {}; + + if (target) { + source = extractParameters(route, source, coding); + Object.keys(target).forEach(function (key) { + source[key] = target[key]; + }); + } + + if (typeof source === 'object') { + return insertParameters(route, source, coding); + } + + if (typeof source === 'string') { + return extractParameters(route, source, coding); + } + }; +}; + +export default route; diff --git a/src/lib/geo.ts b/src/lib/geo.ts new file mode 100644 index 0000000..f4b920b --- /dev/null +++ b/src/lib/geo.ts @@ -0,0 +1,35 @@ +import { Rectangle } from '../components/map/types'; + +// cf https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#ECMAScript_(JavaScript/ActionScript,_etc.) +export const lon2tile = (lon: number, zoom: number) => { + return ((Number(lon) + 180) / 360) * Math.pow(2, zoom); +}; +export const lat2tile = (lat: number, zoom: number) => { + return ( + ((1 - + Math.log( + Math.tan((Number(lat) * Math.PI) / 180) + + 1 / Math.cos((Number(lat) * Math.PI) / 180) + ) / + Math.PI) / + 2) * + Math.pow(2, zoom) + ); +}; + +export function tile2long(x: number, z: number) { + return (x / Math.pow(2, z)) * 360 - 180; +} + +export function tile2lat(y: number, z: number) { + var n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z); + return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); +} + +export const rectanglesIntersect = (r1: Rectangle, r2: Rectangle) => + !( + r2.topLeft.x > r1.bottomRight.x || + r2.bottomRight.x < r1.topLeft.x || + r2.topLeft.y > r1.bottomRight.y || + r2.bottomRight.y < r1.topLeft.y + ); diff --git a/src/lib/gpx-parser-builder/LICENSE b/src/lib/gpx-parser-builder/LICENSE new file mode 100644 index 0000000..f975d73 --- /dev/null +++ b/src/lib/gpx-parser-builder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Zheng-Xiang Ke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/lib/gpx-parser-builder/README.md b/src/lib/gpx-parser-builder/README.md new file mode 100644 index 0000000..88d2602 --- /dev/null +++ b/src/lib/gpx-parser-builder/README.md @@ -0,0 +1,91 @@ +# gpx-parser-builder +A simple gpx parser and builder between GPX string and JavaScript object. It is dependent on [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js). + +[![npm](https://img.shields.io/npm/dt/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder) +[![GitHub stars](https://img.shields.io/github/stars/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/network) +[![npm](https://img.shields.io/npm/v/gpx-parser-builder.svg)](https://www.npmjs.com/package/gpx-parser-builder) +[![GitHub license](https://img.shields.io/github/license/kf99916/gpx-parser-builder.svg)](https://github.com/kf99916/gpx-parser-builder/blob/master/LICENSE) + +## Requirements + +gpx-parser-builder is written with ECMAScript 6. You can leverage [Babel](https://babeljs.io/) and [Webpack](https://webpack.js.org/) to make all browsers available. + +## Installation + +```bash +npm install gpx-parser-builder --save +``` + +## Version + +v1.0.0+ is a breaking change for v0.2.2-. v1.0.0+ fully supports gpx files including waypoints, routes, and tracks. Every gpx type is 1-1 corresponding to a JavaScript class. + +## Usage + +```javascript +import GPX from 'gpx-parser-builder'; + +// Parse gpx +const gpx = GPX.parse('GPX_STRING'); + +window.console.dir(gpx.metadata); +window.console.dir(gpx.wpt); +window.console.dir(gpx.trk); + +// Build gpx +window.console.log(gpx.toString()); +``` + +Get more details about usage with the unit tests. + +### GPX + +The GPX JavaScript object. + +`constructor(object)` + +```javascript +const gpx = new Gpx({$:{...}, metadat: {...}, wpt:[{...},{...}]}, trk: {...}, rte: {...}) +``` + +#### Member Variables + +`$` the attributes for the gpx element. Default value: +```javascript +{ + 'version': '1.1', + 'creator': 'gpx-parser-builder', + 'xmlns': 'http://www.topografix.com/GPX/1/1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' +} +``` + +`metadata` the metadata for the gpx. + +`wpt` array of waypoints. It is corresponded to ``. The type of all elements in `wpt` is `Waypoint`; + +`rte` array of routes. It is corresponded to ``. The type of all elements in `rte` is `Route`; + +`trk` array of tracks. It is corresponded to ``. The type of all elements in `trk` is `Track`; + +#### Static Methods + +`parse(gpxString)` parse gpx string to Gpx object. return `null` if parsing failed. + +#### Member Methods + +`toString(options)` GPX object to gpx string. The options is for [isomorphic-xml2js](https://github.com/RikkiGibson/isomorphic-xml2js). + +## Save as GPX file in the frontend + +You can leverage [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js) or [FileSaver.js](https://github.com/eligrey/FileSaver.js) to save as GPX file. ⚠️Not all borwsers support the above file techniques. ⚠️️️ + +## Author + +Zheng-Xiang Ke, kf99916@gmail.com + +## License + +gpx-parser-builder is available under the MIT license. See the LICENSE file for more info. diff --git a/src/lib/gpx-parser-builder/package.json b/src/lib/gpx-parser-builder/package.json new file mode 100644 index 0000000..19b2cdc --- /dev/null +++ b/src/lib/gpx-parser-builder/package.json @@ -0,0 +1,36 @@ +{ + "name": "gpx-parser-builder", + "version": "1.0.2", + "description": "A simple gpx parser and builder between GPX string and JavaScript object", + "main": "./src/gpx.js", + "scripts": { + "test": "mocha --require @babel/register test/**/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kf99916/gpx-parser-builder.git" + }, + "keywords": [ + "gpx", + "parser", + "builder" + ], + "author": "Zheng-Xiang Ke", + "license": "MIT", + "bugs": { + "url": "https://github.com/kf99916/gpx-parser-builder/issues" + }, + "homepage": "https://github.com/kf99916/gpx-parser-builder", + "files": [ + "src" + ], + "devDependencies": { + "@babel/core": "~7.7", + "@babel/preset-env": "~7.7", + "@babel/register": "~7.7", + "mocha": "~6.2" + }, + "dependencies": { + "isomorphic-xml2js": "~0.1" + } +} diff --git a/src/lib/gpx-parser-builder/src/bounds.js b/src/lib/gpx-parser-builder/src/bounds.js new file mode 100644 index 0000000..08f45b4 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/bounds.js @@ -0,0 +1,8 @@ +export default class Bounds { + constructor(object) { + this.minlat = object.minlat; + this.minlon = object.minlon; + this.maxlat = object.maxlat; + this.maxlon = object.maxlon; + } +} diff --git a/src/lib/gpx-parser-builder/src/copyright.js b/src/lib/gpx-parser-builder/src/copyright.js new file mode 100644 index 0000000..5a9f77e --- /dev/null +++ b/src/lib/gpx-parser-builder/src/copyright.js @@ -0,0 +1,7 @@ +export default class Copyright { + constructor(object) { + this.author = object.author; + this.year = object.year; + this.license = object.license; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts b/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts new file mode 100644 index 0000000..37665f0 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/gpx-parser-builder.d.ts @@ -0,0 +1,13 @@ +declare module 'gpx-parser-builder' { + class GPX { + static parse(gpxString: any): any; + constructor(object: any); + $: any; + extensions: any; + metadata: any; + wpt: any; + rte: any; + trk: any; + toString(options: any): string; + } +} diff --git a/src/lib/gpx-parser-builder/src/gpx.js b/src/lib/gpx-parser-builder/src/gpx.js new file mode 100644 index 0000000..07be870 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/gpx.js @@ -0,0 +1,79 @@ +import * as xml2js from 'isomorphic-xml2js'; +import Metadata from './metadata'; +import Waypoint from './waypoint'; +import Route from './route'; +import Track from './track'; +import {removeEmpty, allDatesToISOString} from './utils'; + +const defaultAttributes = { + version: '1.1', + creator: 'gpx-parser-builder', + xmlns: 'http://www.topografix.com/GPX/1/1', + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation': + 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' +} + +export default class GPX { + constructor(object) { + this.$ = Object.assign({}, defaultAttributes, object.$ || object.attributes || {}); + this.extensions = object.extensions; + + if (object.metadata) { + this.metadata = new Metadata(object.metadata); + } + if (object.wpt) { + if (!Array.isArray(object.wpt)) { + object.wpt = [object.wpt]; + } + this.wpt = object.wpt.map(wpt => new Waypoint(wpt)); + } + if (object.rte) { + if (!Array.isArray(object.rte)) { + object.rte = [object.rte]; + } + this.rte = object.rte.map(rte => new Route(rte)); + } + if (object.trk) { + if (!Array.isArray(object.trk)) { + object.trk = [object.trk]; + } + this.trk = object.trk.map(trk => new Track(trk)); + } + + removeEmpty(this); + } + + static parse(gpxString) { + let gpx; + xml2js.parseString(gpxString, { + explicitArray: false + }, (err, xml) => { + if (err) { + return; + } + if (!xml.gpx) { + return; + } + + gpx = new GPX({ + attributes: xml.gpx.$, + metadata: xml.gpx.metadata, + wpt: xml.gpx.wpt, + rte: xml.gpx.rte, + trk: xml.gpx.trk + }); + }); + + return gpx; + } + + toString(options) { + options = options || {}; + options.rootName = 'gpx'; + + const builder = new xml2js.Builder(options), gpx = new GPX(this); + allDatesToISOString(gpx); + return builder.buildObject(gpx); + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/link.js b/src/lib/gpx-parser-builder/src/link.js new file mode 100644 index 0000000..f7e05af --- /dev/null +++ b/src/lib/gpx-parser-builder/src/link.js @@ -0,0 +1,8 @@ +export default class Link { + constructor(object) { + this.$ = {}; + this.$.href = object.$.href || object.href; + this.text = object.text; + this.type = object.type; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/metadata.js b/src/lib/gpx-parser-builder/src/metadata.js new file mode 100644 index 0000000..293bbec --- /dev/null +++ b/src/lib/gpx-parser-builder/src/metadata.js @@ -0,0 +1,29 @@ +import Copyright from './copyright'; +import Link from './link'; +import Person from './person'; +import Bounds from './bounds'; + +export default class Metadata { + constructor(object) { + this.name = object.name; + this.desc = object.desc; + this.time = object.time ? new Date(object.time) : new Date(); + this.keywords = object.keywords; + this.extensions = object.extensions; + if (object.author) { + this.author = new Person(object.author); + } + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + if (object.bounds) { + this.bounds = new Bounds(object.bounds); + } + if (object.copyright) { + this.copyright = new Copyright(object.copyright); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/person.js b/src/lib/gpx-parser-builder/src/person.js new file mode 100644 index 0000000..24b44c4 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/person.js @@ -0,0 +1,11 @@ +import Link from './link'; + +export default class Person { + constructor(object) { + this.name = object.name; + this.email = object.email; + if (object.link) { + this.link = new Link(object.link); + } + } +} diff --git a/src/lib/gpx-parser-builder/src/route.js b/src/lib/gpx-parser-builder/src/route.js new file mode 100644 index 0000000..842e9f8 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/route.js @@ -0,0 +1,27 @@ +import Waypoint from './waypoint'; +import Link from './link'; + +export default class Route { + constructor(object) { + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.number = object.number; + this.type = object.type; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + this.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + + if (object.rtept) { + if (!Array.isArray(object.rtept)) { + this.rtept = [object.rtept]; + } + this.rtept = object.rtept.map(pt => new Waypoint(pt)); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/track-segment.js b/src/lib/gpx-parser-builder/src/track-segment.js new file mode 100644 index 0000000..5fca2d5 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/track-segment.js @@ -0,0 +1,13 @@ +import Waypoint from './waypoint'; + +export default class TrackSegment { + constructor(object) { + if (object.trkpt) { + if (!Array.isArray(object.trkpt)) { + object.trkpt = [object.trkpt]; + } + this.trkpt = object.trkpt.map(pt => new Waypoint(pt)); + } + this.extensions = object.extensions; + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/track.js b/src/lib/gpx-parser-builder/src/track.js new file mode 100644 index 0000000..4e330aa --- /dev/null +++ b/src/lib/gpx-parser-builder/src/track.js @@ -0,0 +1,26 @@ +import TrackSegment from './track-segment'; +import Link from './link'; + +export default class Track { + constructor(object) { + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.number = object.number; + this.type = object.type; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + if (object.trkseg) { + if (!Array.isArray(object.trkseg)) { + object.trkseg = [object.trkseg]; + } + this.trkseg = object.trkseg.map(seg => new TrackSegment(seg)); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx-parser-builder/src/utils.js b/src/lib/gpx-parser-builder/src/utils.js new file mode 100644 index 0000000..d769918 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/utils.js @@ -0,0 +1,23 @@ +function removeEmpty(obj) { + Object.entries(obj).forEach(([key, val]) => { + if (val && val instanceof Object) { + removeEmpty(val); + } else if (val == null) { + delete obj[key]; + } + }); +} + +function allDatesToISOString(obj) { + Object.entries(obj).forEach(([key, val]) => { + if (val) { + if (val instanceof Date) { + obj[key] = val.toISOString().split('.')[0] + 'Z'; + } else if (val instanceof Object) { + allDatesToISOString(val); + } + } + }); +} + +export { removeEmpty, allDatesToISOString }; diff --git a/src/lib/gpx-parser-builder/src/waypoint.js b/src/lib/gpx-parser-builder/src/waypoint.js new file mode 100644 index 0000000..2910ff8 --- /dev/null +++ b/src/lib/gpx-parser-builder/src/waypoint.js @@ -0,0 +1,32 @@ +import Link from './link'; + +export default class Waypoint { + constructor(object) { + this.$ = {}; + this.$.lat = object.$.lat === 0 || object.lat === 0 ? 0 : object.$.lat || object.lat || -1; + this.$.lon = object.$.lon === 0 || object.lon === 0 ? 0 : object.$.lon || object.lon || -1; + this.ele = object.ele; + this.time = object.time ? new Date(object.time) : new Date(); + this.magvar = object.magvar; + this.geoidheight = object.geoidheight; + this.name = object.name; + this.cmt = object.cmt; + this.desc = object.desc; + this.src = object.src; + this.sym = object.sym; + this.type = object.type; + this.sat = object.sat; + this.hdop = object.hdop; + this.vdop = object.vdop; + this.pdop = object.pdop; + this.ageofdgpsdata = object.ageofdgpsdata; + this.dgpsid = object.dgpsid; + this.extensions = object.extensions; + if (object.link) { + if (!Array.isArray(object.link)) { + object.link = [object.link]; + } + this.link = object.link.map(l => new Link(l)); + } + } +} \ No newline at end of file diff --git a/src/lib/gpx.test.ts b/src/lib/gpx.test.ts new file mode 100644 index 0000000..ca8ce20 --- /dev/null +++ b/src/lib/gpx.test.ts @@ -0,0 +1,33 @@ +import { assert } from 'console'; +import { findStartTime } from './gpx'; + +describe('findStartTime', () => { + test('to be undefined for a string', () => { + const start = findStartTime(''); + expect(start).toBeUndefined(); + }); + test('to be undefined for an object without time key', () => { + const start = findStartTime({ foo: 'foo', bar: 'bar' }); + expect(start).toBeUndefined(); + }); + test('to be the time value for an object with a time key', () => { + const start = findStartTime({ foo: 'foo', time: 'bar' }); + expect(start).toEqual('bar'); + }); + test('to be the lowest time value for an object with several time keys', () => { + const start = findStartTime({ + foo: { time: 'foo' }, + time: 'bar', + bar: { time: 'a' }, + }); + expect(start).toEqual('a'); + }); + test('to be the lowest time value for an array with several objects with time keys', () => { + const start = findStartTime({ + foos: [{ time: 'foo' }, { time: '0' }], + time: 'bar', + bar: { time: 'a' }, + }); + expect(start).toEqual('0'); + }); +}); diff --git a/src/lib/gpx.ts b/src/lib/gpx.ts new file mode 100644 index 0000000..f3f32e8 --- /dev/null +++ b/src/lib/gpx.ts @@ -0,0 +1,19 @@ +const min = (s1?: string, s2?: string) => { + return s1! < s2! ? s1 : s2; +}; + +export const findStartTime = (x: any, startTime?: string) => { + if (typeof x === 'object') { + let newStartTime = startTime; + + for (const key in x) { + if (key === 'time') { + newStartTime = min(newStartTime, x[key]); + } else { + newStartTime = findStartTime(x[key], newStartTime); + } + } + return newStartTime; + } + else return startTime; +}; diff --git a/src/lib/ids.test.ts b/src/lib/ids.test.ts new file mode 100644 index 0000000..785c81a --- /dev/null +++ b/src/lib/ids.test.ts @@ -0,0 +1,120 @@ +import { route } from './docuri'; +import uri from './ids'; + +describe('Checking some DocURI features', () => { + test(', basic route', () => { + const gpx = route('gpx/:id'); + expect(gpx({ id: 10 })).toBe('gpx/10'); + }); + test(', basic route (vice-versa', () => { + const gpx = route('gpx/:id'); + expect(gpx('gpx/10')).toMatchObject({ id: '10' }); + }); +}); + +describe('Checking a multilevel route', () => { + test(', using the two levels', () => { + const gpx = route('gpx/:gpx/3trk/:trk'); + expect(gpx({ gpx: 10, trk: 0 })).toBe('gpx/10/3trk/0'); + }); + test(', using the two levels (vive-versa)', () => { + const gpx = route('gpx/:gpx/3trk/:trk'); + expect(gpx('gpx/10/3trk/0')).toMatchObject({ gpx: '10', trk: '0' }); + }); +}); + +describe('Checking a multilevel route with optional part', () => { + test(', using the two levels', () => { + const gpx = route('gpx/:gpx(/3trk/:trk)'); + expect(gpx({ gpx: 10, trk: 0 })).toBe('gpx/10/3trk/0'); + }); + test(', using the two levels (vive-versa)', () => { + const gpx = route('gpx/:gpx(/3trk/:trk)'); + expect(gpx('gpx/10/3trk/0')).toMatchObject({ gpx: '10', trk: '0' }); + }); + test(', using only one level', () => { + const gpx = route('gpx/:gpx(/3trk/:trk)'); + expect(gpx({ gpx: 10 })).toBe('gpx/10/3trk/'); //Unfortunately ! + }); + test(', using only one level (vive-versa)', () => { + const gpx = route('gpx/:gpx(/3trk/:trk)'); + expect(gpx('gpx/10')).toMatchObject({ gpx: '10' }); + }); +}); + +describe('Checking gpx ids', () => { + const id = { + gpx: 1234567890123456, + }; + const key = 'gpx/1234567890123456'; + test(', vice', () => { + const gpx = uri('gpx', id); + expect(gpx).toBe(key); + }); + test(', and versa', () => { + const gpx = uri('gpx', key); + expect(gpx).toMatchObject(id); + }); +}); + +describe('Checking trk ids', () => { + const id = { + gpx: 1234567890123456, + trk: 123456, + }; + const key = 'gpx/1234567890123456/3trk/123456'; + test(', vice', () => { + const rte = uri('trk', id); + expect(rte).toBe(key); + }); + test(', and versa', () => { + const rte = uri('trk', key); + expect(rte).toMatchObject(id); + }); +}); + +describe('Checking trkseg ids', () => { + const id = { + gpx: 111, + trk: 0, + trkseg: 3, + }; + const key = 'gpx/0000000000000111/3trk/000000/000003'; + test(', vice', () => { + const rte = uri('trkseg', id); + expect(rte).toBe(key); + }); + test(', and versa', () => { + const rte = uri('trkseg', key); + expect(rte).toMatchObject(id); + }); +}); + +describe('Checking trkpt ids', () => { + const id = { + gpx: 25, + trk: 8, + trkseg: 0, + trkpt: 155, + }; + const key = 'gpx/0000000000000025/3trk/000008/000000/000155'; + test(', vice', () => { + const rte = uri('trkpt', id); + expect(rte).toBe(key); + }); + test(', and versa', () => { + const rte = uri('trkpt', key); + expect(rte).toMatchObject(id); + }); +}); + +describe('Checking settings id', () => { + test(', vice', () => { + const rte = uri('settings', {}); + expect(rte).toBe('settings'); + }); + test(', and versa', () => { + const rte = uri('settings', 'settings'); + expect(rte).toMatchObject({}); + }); +}); diff --git a/src/lib/ids.ts b/src/lib/ids.ts new file mode 100644 index 0000000..4db603f --- /dev/null +++ b/src/lib/ids.ts @@ -0,0 +1,51 @@ +import route from './docuri'; + +const integerType = (n: number) => { + return { + encoder: (v: number) => v.toString().padStart(n, '0'), + decoder: parseInt, + }; +}; + +const bigIntType = (n: number) => { + return { + encoder: (v: number) => v.toString().padStart(n, '0'), + decoder: BigInt, + }; +}; + +const coding = { + gpx: integerType(16), + wpt: integerType(6), + rte: integerType(6), + rtept: integerType(6), + trk: integerType(6), + trkseg: integerType(6), + trkpt: integerType(6), +}; + +const routes = { + dbdef: route('dbdef', coding), + settings: route('settings', coding), + gpx: route('gpx/:gpx', coding), + wpt: route('gpx/:gpx/1wpt/:wpt', coding), + rte: route('gpx/:gpx/2rte/:rte', coding), + rtept: route('gpx/:gpx/2rte/:rte/:rtept', coding), + trk: route('gpx/:gpx/3trk/:trk', coding), + trkseg: route('gpx/:gpx/3trk/:trk/:trkseg', coding), + trkpt: route('gpx/:gpx/3trk/:trk/:trkseg/:trkpt', coding), + extensions: route('gpx/:gpx/4extensions', coding), +}; + +type RouteKey = keyof typeof routes; + +const uri = (type: RouteKey, param: any) => { + return routes[type](param); +}; + +export default uri; + +const minDate = -8640000000000000; +const halfMinDate = minDate / 2; + +export const intToGpxId = (i: number) => Math.round(i / 2) - halfMinDate; diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts new file mode 100644 index 0000000..e05ac25 --- /dev/null +++ b/src/lib/types.d.ts @@ -0,0 +1,34 @@ +interface IdGpx { + gpx: number; +} + +interface IdTrk { + gpx: number; + trk: number; +} +interface IdTrkseg { + gpx: number; + trk: number; + trkseg: number; +} +interface IdTrkpt { + gpx: number; + trk: number; + trkseg: number; + trkpt: number; +} +interface IdWpt { + gpx: number; + wpt: number; +} + +interface IdRte { + gpx: number; + rte: number; +} + +interface IdRtept { + gpx: number; + rte: number; + rtept: number; +} diff --git a/src/missing-typedefs/docuri.d.ts b/src/missing-typedefs/docuri.d.ts new file mode 100644 index 0000000..d3cf061 --- /dev/null +++ b/src/missing-typedefs/docuri.d.ts @@ -0,0 +1 @@ +declare module 'docuri'; \ No newline at end of file diff --git a/src/missing-typedefs/gpx-parser-builder.d.ts b/src/missing-typedefs/gpx-parser-builder.d.ts new file mode 100644 index 0000000..d50663c --- /dev/null +++ b/src/missing-typedefs/gpx-parser-builder.d.ts @@ -0,0 +1 @@ +declare module 'gpx-parser-builder'; \ No newline at end of file diff --git a/src/workers/dispatcher-main.test.js b/src/workers/dispatcher-main.test.js new file mode 100644 index 0000000..fac3fe6 --- /dev/null +++ b/src/workers/dispatcher-main.test.js @@ -0,0 +1,37 @@ +import dispatch, { init, worker } from './dispatcher-main'; + +jest.mock('./get-worker', () => ({ + getWorker: () => ({ + port: { + postMessage: jest.fn(), + }, + }), +})); + +describe('The dispatcher-main', () => { + beforeEach(() => { + init(); + }); + test('should create a new shared web worker', () => { + expect(worker).toBeDefined(); + }); + test('should create a onmessage function', () => { + expect(worker.port.onmessage).toBeDefined(); + }); + test('should return a promise if no callback is provided', () => { + expect(dispatch('ping')).toBeInstanceOf(Promise); + }); + test('should forward the message', () => { + dispatch('ping'); + expect(worker.port.postMessage).toBeCalledWith({ id: 0, payload: 'ping' }); + }); + test('should return the response', () => { + var response; + const callback = (error, success) => { + response = success; + }; + dispatch('ping', callback); + worker.port.onmessage({ data: { id: 0, payload: 'pong' } }); + expect(response).toEqual('pong'); + }); +}); diff --git a/src/workers/dispatcher-main.ts b/src/workers/dispatcher-main.ts new file mode 100644 index 0000000..f212569 --- /dev/null +++ b/src/workers/dispatcher-main.ts @@ -0,0 +1,53 @@ +import { getWorker } from './get-worker'; + +declare global { + var dispatcherQueue: { index: number; queue: Map }; +} + +export var worker: any; + +export const init = () => { + console.log({ caller: 'dispatcher-main / init' }); + globalThis.dispatcherQueue = { index: 0, queue: new Map() }; + console.log({ caller: 'dispatcher-main / init', globalThis }); + + worker = getWorker(); + console.log({ caller: 'dispatcher-main / init', worker }); + + worker.onmessage = (event: any) => { + const { id, payload } = event.data; + dispatcherQueue.queue.get(id)(null, payload); + dispatcherQueue.queue.delete(id); + }; +}; + +const dispatch = ( + payload: any, + callBack?: (error: any, result: any) => void +) => { + if (worker === undefined) { + init(); + } + if (callBack === undefined) { + /** If a callback function is not provided, return a promise */ + return new Promise((resolve, reject) => { + dispatch(payload, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + /** Otherwise, use the callback function */ + dispatcherQueue.queue.set(dispatcherQueue.index, callBack); + const message = { + id: dispatcherQueue.index++, + payload: payload, + }; + worker.postMessage(message); + console.log({ caller: 'dispatcher-main / dispatch', message }); +}; + +export default dispatch; diff --git a/src/workers/dispatcher-worker.test.js b/src/workers/dispatcher-worker.test.js new file mode 100644 index 0000000..7db3e71 --- /dev/null +++ b/src/workers/dispatcher-worker.test.js @@ -0,0 +1,33 @@ +import worker from './dispatcher-worker'; + +jest.mock('../db', () => ({ + initDb: () => 'called initDb', +})); + +describe('The dispatcher-worker ', () => { + let port; + beforeEach(() => { + port = { + postMessage: jest.fn(), + }; + worker.onconnect({ ports: [port] }); + }); + test('creates a onmessage function', () => { + expect(port.onmessage).toBeDefined(); + expect(port.postMessage).not.toBeCalled(); + }); + test('receives a ping and sends back an unknownAction', async () => { + await port.onmessage({ data: { id: 5, payload: { action: 'ping' } } }); + expect(port.postMessage).toBeCalledWith({ + id: 5, + payload: 'unknownAction', + }); + }); + test('calls initDb when required', async () => { + await port.onmessage({ data: { id: 5, payload: { action: 'initDb' } } }); + expect(port.postMessage).toBeCalledWith({ + id: 5, + payload: 'called initDb', + }); + }); +}); diff --git a/src/workers/dispatcher-worker.ts b/src/workers/dispatcher-worker.ts new file mode 100644 index 0000000..a32090c --- /dev/null +++ b/src/workers/dispatcher-worker.ts @@ -0,0 +1,42 @@ +/// +import { initDb } from '../db'; +import { + putNewGpx, + existsGpx, + pruneAndSaveImportedGpx, + getGpxesForViewport, + getGpx, +} from '../db/gpx'; +import { getTrk, putNewTrk } from '../db/trk'; +import { getTrkseg } from '../db/trkseg'; + +const self = globalThis as unknown as WorkerGlobalScope; + +const actions = { + initDb, + putNewGpx, + putNewTrk, + existsGpx, + pruneAndSaveImportedGpx, + getGpxesForViewport, + getGpx, + getTrk, + getTrkseg, +}; + +console.log({ caller: 'dispatcher-worker', actions }); + +onmessage = async function (e) { + console.log({ caller: 'dispatcher-worker / onmessage', e }); + const { id, payload } = e.data; + console.log(`payload.action in actions: ${payload.action in actions}`); + var returnValue: any = 'unknownAction'; + if (payload.action in actions) { + returnValue = await actions[payload.action]( + payload.params + ); + } + postMessage({ id: id, payload: returnValue }); +}; + +//export default self; diff --git a/src/workers/get-worker.ts b/src/workers/get-worker.ts new file mode 100644 index 0000000..8f1dac8 --- /dev/null +++ b/src/workers/get-worker.ts @@ -0,0 +1,8 @@ +export const getWorker = () => { + console.log({ caller: 'getWorker' }); + return new Worker(new URL('./dispatcher-worker', import.meta.url), { + type: 'module', + }); +}; + +export default getWorker; diff --git a/vite.config.ts b/vite.config.ts index fde606f..0ddd9e3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,14 @@ import { defineConfig } from 'vite'; import solidPlugin from 'vite-plugin-solid'; -export default defineConfig({ +// yarn add --dev @esbuild-plugins/node-globals-polyfill +import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'; +// yarn add --dev @esbuild-plugins/node-modules-polyfill +import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'; +// You don't need to add this to deps, it's included by @esbuild-plugins/node-modules-polyfill +import rollupNodePolyFill from 'rollup-plugin-node-polyfills'; + +export default defineConfig(({ command, mode, ssrBuild }) => ({ plugins: [solidPlugin()], test: { environment: 'jsdom', @@ -29,5 +36,49 @@ export default defineConfig({ }, resolve: { conditions: ['development', 'browser'], + alias: { + // This Rollup aliases are extracted from @esbuild-plugins/node-modules-polyfill, + // see https://github.com/remorses/esbuild-plugins/blob/master/node-modules-polyfill/src/polyfills.ts + // process and buffer are excluded because already managed + // by node-globals-polyfill + util: 'rollup-plugin-node-polyfills/polyfills/util', + sys: 'util', + events: 'rollup-plugin-node-polyfills/polyfills/events', + stream: 'rollup-plugin-node-polyfills/polyfills/stream', + path: 'rollup-plugin-node-polyfills/polyfills/path', + querystring: 'rollup-plugin-node-polyfills/polyfills/qs', + punycode: 'rollup-plugin-node-polyfills/polyfills/punycode', + url: 'rollup-plugin-node-polyfills/polyfills/url', + string_decoder: 'rollup-plugin-node-polyfills/polyfills/string-decoder', + http: 'rollup-plugin-node-polyfills/polyfills/http', + https: 'rollup-plugin-node-polyfills/polyfills/http', + os: 'rollup-plugin-node-polyfills/polyfills/os', + assert: 'rollup-plugin-node-polyfills/polyfills/assert', + constants: 'rollup-plugin-node-polyfills/polyfills/constants', + _stream_duplex: + 'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex', + _stream_passthrough: + 'rollup-plugin-node-polyfills/polyfills/readable-stream/passthrough', + _stream_readable: + 'rollup-plugin-node-polyfills/polyfills/readable-stream/readable', + _stream_writable: + 'rollup-plugin-node-polyfills/polyfills/readable-stream/writable', + _stream_transform: + 'rollup-plugin-node-polyfills/polyfills/readable-stream/transform', + timers: 'rollup-plugin-node-polyfills/polyfills/timers', + console: 'rollup-plugin-node-polyfills/polyfills/console', + vm: 'rollup-plugin-node-polyfills/polyfills/vm', + zlib: 'rollup-plugin-node-polyfills/polyfills/zlib', + tty: 'rollup-plugin-node-polyfills/polyfills/tty', + domain: 'rollup-plugin-node-polyfills/polyfills/domain', + }, }, -}); + define: + command === 'serve' + ? { + // By default, Vite doesn't include shims for NodeJS/ + // necessary for segment analytics lib to work + global: {}, + } + : {}, +}));