diff --git a/deno.json b/deno.json index 9141128..12f7842 100644 --- a/deno.json +++ b/deno.json @@ -24,10 +24,10 @@ "@js-temporal/polyfill": "npm:@js-temporal/polyfill@~0.4.4", "@lukeed/uuid": "npm:@lukeed/uuid@^2.0.1", "@preact/signals-core": "npm:@preact/signals-core@^1.8.0", - "@uwdata/mosaic-core": "npm:@uwdata/mosaic-core@~0.10.0", - "@uwdata/mosaic-plot": "npm:@uwdata/mosaic-plot@~0.10.0", - "@uwdata/mosaic-sql": "npm:@uwdata/mosaic-sql@~0.10.0", - "apache-arrow": "npm:apache-arrow@^17.0.0", + "@uwdata/flechette": "npm:@uwdata/flechette@^1.1.0", + "@uwdata/mosaic-core": "npm:@uwdata/mosaic-core@~0.11.0", + "@uwdata/mosaic-plot": "npm:@uwdata/mosaic-plot@~0.11.0", + "@uwdata/mosaic-sql": "npm:@uwdata/mosaic-sql@~0.11.0", "d3": "npm:d3@^7.9.0", "htl": "npm:htl@~0.3.1" }, diff --git a/deno.lock b/deno.lock index c7d167c..085693c 100644 --- a/deno.lock +++ b/deno.lock @@ -2,18 +2,54 @@ "version": "3", "packages": { "specifiers": { + "jsr:@luca/esbuild-deno-loader@^0.10.3": "jsr:@luca/esbuild-deno-loader@0.10.3", + "jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1", + "jsr:@std/encoding@0.213": "jsr:@std/encoding@0.213.1", + "jsr:@std/jsonc@0.213": "jsr:@std/jsonc@0.213.1", + "jsr:@std/path@0.213": "jsr:@std/path@0.213.1", "npm:@anywidget/types": "npm:@anywidget/types@0.1.9", + "npm:@anywidget/types@0.2.0": "npm:@anywidget/types@0.2.0", "npm:@js-temporal/polyfill@~0.4.4": "npm:@js-temporal/polyfill@0.4.4", "npm:@lukeed/uuid@^2.0.1": "npm:@lukeed/uuid@2.0.1", "npm:@preact/signals-core@^1.8.0": "npm:@preact/signals-core@1.8.0", "npm:@types/d3": "npm:@types/d3@7.4.3", "npm:@types/d3-scale@4.0.8": "npm:@types/d3-scale@4.0.8", - "npm:@uwdata/mosaic-core@~0.10.0": "npm:@uwdata/mosaic-core@0.10.0", - "npm:@uwdata/mosaic-plot@~0.10.0": "npm:@uwdata/mosaic-plot@0.10.0", - "npm:@uwdata/mosaic-sql@~0.10.0": "npm:@uwdata/mosaic-sql@0.10.0", - "npm:apache-arrow@^17.0.0": "npm:apache-arrow@17.0.0", + "npm:@uwdata/flechette@^1.1.0": "npm:@uwdata/flechette@1.1.0", + "npm:@uwdata/mosaic-core@~0.11.0": "npm:@uwdata/mosaic-core@0.11.0", + "npm:@uwdata/mosaic-plot@~0.11.0": "npm:@uwdata/mosaic-plot@0.11.0", + "npm:@uwdata/mosaic-sql@~0.11.0": "npm:@uwdata/mosaic-sql@0.11.0", + "npm:d3@^7.9.0": "npm:d3@7.9.0_d3-selection@3.0.0", + "npm:esbuild@0.20.2": "npm:esbuild@0.20.2", "npm:htl@~0.3.1": "npm:htl@0.3.1" }, + "jsr": { + "@luca/esbuild-deno-loader@0.10.3": { + "integrity": "32fc93f7e7f78060234fd5929a740668aab1c742b808c6048b57f9aaea514921", + "dependencies": [ + "jsr:@std/encoding@0.213", + "jsr:@std/jsonc@0.213", + "jsr:@std/path@0.213" + ] + }, + "@std/assert@0.213.1": { + "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" + }, + "@std/encoding@0.213.1": { + "integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62" + }, + "@std/jsonc@0.213.1": { + "integrity": "5578f21aa583b7eb7317eed077ffcde47b294f1056bdbb9aacec407758637bfe", + "dependencies": [ + "jsr:@std/assert@^0.213.1" + ] + }, + "@std/path@0.213.1": { + "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", + "dependencies": [ + "jsr:@std/assert@^0.213.1" + ] + } + }, "npm": { "@75lb/deep-merge@1.1.2": { "integrity": "sha512-08K9ou5VNbheZFxM5tDWoqjA3ImC50DiuuJ2tj1yEPRfkp8lLLg6XAaJ4On+a0yAXor/8ay5gHnAIshRM44Kpw==", @@ -28,12 +64,108 @@ "@jupyter-widgets/base": "@jupyter-widgets/base@6.0.10" } }, + "@anywidget/types@0.2.0": { + "integrity": "sha512-+XtK4uwxRd4JpuevUMhirrbvC0V4yCA/i0lEjhmSAtOaxiXIg/vBKzaSonDuoZ1a9LEjUXTW2+m7w+ULgsJYvg==", + "dependencies": {} + }, "@duckdb/duckdb-wasm@1.28.1-dev99.0": { "integrity": "sha512-EgIRjUFAos4oT4QeFLK6g7IDPWFWu8dP/mj2/b0pyiN5dDL+xxKW2KyLbeAs26L6Y6yeYy0O4OJSezFOqTbOmg==", "dependencies": { "apache-arrow": "apache-arrow@14.0.2" } }, + "@esbuild/aix-ppc64@0.20.2": { + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "dependencies": {} + }, + "@esbuild/android-arm64@0.20.2": { + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "dependencies": {} + }, + "@esbuild/android-arm@0.20.2": { + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "dependencies": {} + }, + "@esbuild/android-x64@0.20.2": { + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "dependencies": {} + }, + "@esbuild/darwin-arm64@0.20.2": { + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "dependencies": {} + }, + "@esbuild/darwin-x64@0.20.2": { + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "dependencies": {} + }, + "@esbuild/freebsd-arm64@0.20.2": { + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "dependencies": {} + }, + "@esbuild/freebsd-x64@0.20.2": { + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "dependencies": {} + }, + "@esbuild/linux-arm64@0.20.2": { + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "dependencies": {} + }, + "@esbuild/linux-arm@0.20.2": { + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "dependencies": {} + }, + "@esbuild/linux-ia32@0.20.2": { + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "dependencies": {} + }, + "@esbuild/linux-loong64@0.20.2": { + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "dependencies": {} + }, + "@esbuild/linux-mips64el@0.20.2": { + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "dependencies": {} + }, + "@esbuild/linux-ppc64@0.20.2": { + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "dependencies": {} + }, + "@esbuild/linux-riscv64@0.20.2": { + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "dependencies": {} + }, + "@esbuild/linux-s390x@0.20.2": { + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "dependencies": {} + }, + "@esbuild/linux-x64@0.20.2": { + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "dependencies": {} + }, + "@esbuild/netbsd-x64@0.20.2": { + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "dependencies": {} + }, + "@esbuild/openbsd-x64@0.20.2": { + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "dependencies": {} + }, + "@esbuild/sunos-x64@0.20.2": { + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "dependencies": {} + }, + "@esbuild/win32-arm64@0.20.2": { + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "dependencies": {} + }, + "@esbuild/win32-ia32@0.20.2": { + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "dependencies": {} + }, + "@esbuild/win32-x64@0.20.2": { + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "dependencies": {} + }, "@js-temporal/polyfill@0.4.4": { "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", "dependencies": { @@ -271,12 +403,6 @@ "react-is": "react-is@18.3.1" } }, - "@swc/helpers@0.5.12": { - "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==", - "dependencies": { - "tslib": "tslib@2.7.0" - } - }, "@types/backbone@1.4.14": { "integrity": "sha512-85ldQ99fiYTJFBlZuAJRaCdvTZKZ2p1fSs3fVf+6Ub6k1X0g0hNJ0qJ/2FOByyyAQYLtbEz3shX5taKQfBKBDw==", "dependencies": { @@ -288,18 +414,10 @@ "integrity": "sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA==", "dependencies": {} }, - "@types/command-line-args@5.2.3": { - "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", - "dependencies": {} - }, "@types/command-line-usage@5.0.2": { "integrity": "sha512-n7RlEEJ+4x4TS7ZQddTmNSxP+zziEG0TNsMfiRIxcIVXt71ENJ9ojeXmGO3wPoTdn7pJcU2xc3CJYMktNT6DPg==", "dependencies": {} }, - "@types/command-line-usage@5.0.4": { - "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==", - "dependencies": {} - }, "@types/d3-array@3.2.1": { "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "dependencies": {} @@ -493,12 +611,6 @@ "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", "dependencies": {} }, - "@types/node@20.16.2": { - "integrity": "sha512-91s/n4qUPV/wg8eE9KHYW1kouTfDk2FPGjXbBMfRWP/2vg1rCXNQL1OCabwGs0XSdukuK+MwCDXE30QpSeMUhQ==", - "dependencies": { - "undici-types": "undici-types@6.19.8" - } - }, "@types/node@20.3.0": { "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", "dependencies": {} @@ -515,26 +627,30 @@ "integrity": "sha512-HP38xE+GuWGlbSRq9WrZkousaQ7dragtZCruBVMi0oX1migFZavZ3OROKHSkNp/9ouq82zrWtZpg18jFnVN96g==", "dependencies": {} }, - "@uwdata/mosaic-core@0.10.0": { - "integrity": "sha512-0/KpAlzh6TrnwBn6kNtUV5VMB9ktswO9eCWjLVnzr21KREPN/RAUYUi03ZPvdNtHzJchKc3EVCAx5hGFDqdsKQ==", + "@uwdata/flechette@1.1.0": { + "integrity": "sha512-UG25ytXRsElGDSIsvCuEUUkX/XImdKe7jnZF2Y5Q9uYYQH6WO60F7yLJ/sNRBJqHdn4A0QhTAOuOJevDZZcF8Q==", + "dependencies": {} + }, + "@uwdata/mosaic-core@0.11.0": { + "integrity": "sha512-0bW7zl90EXqZoUfIJCrytjeY5KNjZWoYN1of1M0fJ0A+7cxlh08iLTi9XRaptPmL6teIOpOthVV747422BYVcQ==", "dependencies": { "@duckdb/duckdb-wasm": "@duckdb/duckdb-wasm@1.28.1-dev99.0", - "@uwdata/mosaic-sql": "@uwdata/mosaic-sql@0.10.0", - "apache-arrow": "apache-arrow@16.1.0" + "@uwdata/flechette": "@uwdata/flechette@1.1.0", + "@uwdata/mosaic-sql": "@uwdata/mosaic-sql@0.11.0" } }, - "@uwdata/mosaic-plot@0.10.0": { - "integrity": "sha512-QuQkZrYlKM1Cwr6UeRjQlxgXyLV5SMahHdbobnauyV5tU4Bv5lJa6t0LMN1tVp+f04UZPdPimVwL9Wky+jrPIQ==", + "@uwdata/mosaic-plot@0.11.0": { + "integrity": "sha512-WcIvrHqg5brlT8SPV3qFNsjaRI14govLsMrrmjTRA/uofb6kRmvFiNbNn/9+7VRReGzeB0wMiX2bvAriRAdIzA==", "dependencies": { "@observablehq/plot": "@observablehq/plot@0.6.16", - "@uwdata/mosaic-core": "@uwdata/mosaic-core@0.10.0", - "@uwdata/mosaic-sql": "@uwdata/mosaic-sql@0.10.0", + "@uwdata/mosaic-core": "@uwdata/mosaic-core@0.11.0", + "@uwdata/mosaic-sql": "@uwdata/mosaic-sql@0.11.0", "d3": "d3@7.9.0_d3-selection@3.0.0", "isoformat": "isoformat@0.2.1" } }, - "@uwdata/mosaic-sql@0.10.0": { - "integrity": "sha512-tPsgrHO8zTwLFMBR+BiG4fskK9RSbH+nXS9ju8433NDq3qicAq4Z46CnYpCs0vWIR7AUAxMvncqP6YPbiQhOug==", + "@uwdata/mosaic-sql@0.11.0": { + "integrity": "sha512-q2uDGxsfhhXkJYo1CXyo0RdyWsYUsHrwe+9FORfyGuxfU4+0KxhFRQn5njQm4hEejQhGoqvPbvcQxR9g6PnS+w==", "dependencies": {} }, "ajv@8.17.1": { @@ -567,34 +683,6 @@ "tslib": "tslib@2.7.0" } }, - "apache-arrow@16.1.0": { - "integrity": "sha512-G6GiM6tzPDdGnKUnVkvVr1Nt5+hUaCMBISiasMSiJwI5L5GKDv5Du7Avc2kxlFfB/LEK2LTqh2GKSxutMdf8vQ==", - "dependencies": { - "@swc/helpers": "@swc/helpers@0.5.12", - "@types/command-line-args": "@types/command-line-args@5.2.3", - "@types/command-line-usage": "@types/command-line-usage@5.0.4", - "@types/node": "@types/node@20.16.2", - "command-line-args": "command-line-args@5.2.1", - "command-line-usage": "command-line-usage@7.0.3", - "flatbuffers": "flatbuffers@24.3.25", - "json-bignum": "json-bignum@0.0.3", - "tslib": "tslib@2.7.0" - } - }, - "apache-arrow@17.0.0": { - "integrity": "sha512-X0p7auzdnGuhYMVKYINdQssS4EcKec9TCXyez/qtJt32DrIMGbzqiaMiQ0X6fQlQpw8Fl0Qygcv4dfRAr5Gu9Q==", - "dependencies": { - "@swc/helpers": "@swc/helpers@0.5.12", - "@types/command-line-args": "@types/command-line-args@5.2.3", - "@types/command-line-usage": "@types/command-line-usage@5.0.4", - "@types/node": "@types/node@20.16.2", - "command-line-args": "command-line-args@5.2.1", - "command-line-usage": "command-line-usage@7.0.3", - "flatbuffers": "flatbuffers@24.3.25", - "json-bignum": "json-bignum@0.0.3", - "tslib": "tslib@2.7.0" - } - }, "array-back@3.1.0": { "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", "dependencies": {} @@ -904,6 +992,34 @@ "robust-predicates": "robust-predicates@3.0.2" } }, + "esbuild@0.20.2": { + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dependencies": { + "@esbuild/aix-ppc64": "@esbuild/aix-ppc64@0.20.2", + "@esbuild/android-arm": "@esbuild/android-arm@0.20.2", + "@esbuild/android-arm64": "@esbuild/android-arm64@0.20.2", + "@esbuild/android-x64": "@esbuild/android-x64@0.20.2", + "@esbuild/darwin-arm64": "@esbuild/darwin-arm64@0.20.2", + "@esbuild/darwin-x64": "@esbuild/darwin-x64@0.20.2", + "@esbuild/freebsd-arm64": "@esbuild/freebsd-arm64@0.20.2", + "@esbuild/freebsd-x64": "@esbuild/freebsd-x64@0.20.2", + "@esbuild/linux-arm": "@esbuild/linux-arm@0.20.2", + "@esbuild/linux-arm64": "@esbuild/linux-arm64@0.20.2", + "@esbuild/linux-ia32": "@esbuild/linux-ia32@0.20.2", + "@esbuild/linux-loong64": "@esbuild/linux-loong64@0.20.2", + "@esbuild/linux-mips64el": "@esbuild/linux-mips64el@0.20.2", + "@esbuild/linux-ppc64": "@esbuild/linux-ppc64@0.20.2", + "@esbuild/linux-riscv64": "@esbuild/linux-riscv64@0.20.2", + "@esbuild/linux-s390x": "@esbuild/linux-s390x@0.20.2", + "@esbuild/linux-x64": "@esbuild/linux-x64@0.20.2", + "@esbuild/netbsd-x64": "@esbuild/netbsd-x64@0.20.2", + "@esbuild/openbsd-x64": "@esbuild/openbsd-x64@0.20.2", + "@esbuild/sunos-x64": "@esbuild/sunos-x64@0.20.2", + "@esbuild/win32-arm64": "@esbuild/win32-arm64@0.20.2", + "@esbuild/win32-ia32": "@esbuild/win32-ia32@0.20.2", + "@esbuild/win32-x64": "@esbuild/win32-x64@0.20.2" + } + }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dependencies": {} @@ -922,10 +1038,6 @@ "integrity": "sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ==", "dependencies": {} }, - "flatbuffers@24.3.25": { - "integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ==", - "dependencies": {} - }, "has-flag@4.0.0": { "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dependencies": {} @@ -1121,10 +1233,6 @@ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "dependencies": {} }, - "undici-types@6.19.8": { - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dependencies": {} - }, "url-parse@1.5.10": { "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dependencies": { @@ -1186,10 +1294,10 @@ "npm:@js-temporal/polyfill@~0.4.4", "npm:@lukeed/uuid@^2.0.1", "npm:@preact/signals-core@^1.8.0", - "npm:@uwdata/mosaic-core@~0.10.0", - "npm:@uwdata/mosaic-plot@~0.10.0", - "npm:@uwdata/mosaic-sql@~0.10.0", - "npm:apache-arrow@^17.0.0", + "npm:@uwdata/flechette@^1.1.0", + "npm:@uwdata/mosaic-core@~0.11.0", + "npm:@uwdata/mosaic-plot@~0.11.0", + "npm:@uwdata/mosaic-sql@~0.11.0", "npm:d3@^7.9.0", "npm:htl@~0.3.1" ] diff --git a/lib/clients/DataTable.ts b/lib/clients/DataTable.ts index 8fc629a..00e4b7d 100644 --- a/lib/clients/DataTable.ts +++ b/lib/clients/DataTable.ts @@ -1,4 +1,4 @@ -import * as arrow from "apache-arrow"; +import * as flech from "@uwdata/flechette"; // @ts-types="../deps/mosaic-core.d.ts" import { type Coordinator, @@ -24,12 +24,13 @@ import { StatusBar } from "./StatusBar.ts"; interface DataTableOptions { table: string; - schema: arrow.Schema; + schema: flech.Schema; height?: number; } // TODO: more type ColumnSummaryClient = Histogram | ValueCounts; +type TableRow = Record; /** * Create a DataTable client. @@ -67,7 +68,7 @@ export async function datatable( export class DataTable extends MosaicClient { /** source of the data */ - #meta: { table: string; schema: arrow.Schema }; + #meta: { table: string; schema: flech.Schema }; /** for the component */ #root: HTMLElement = document.createElement("div"); /** shadow root for the component */ @@ -97,8 +98,8 @@ export class DataTable extends MosaicClient { /** the formatter for the data table entries */ #format: Record string>; - /** @type {AsyncBatchReader | null} */ - #reader: AsyncBatchReader | null = null; + /** @type {AsyncBatchReader | null} */ + #reader: AsyncBatchReader | null = null; #sql = signal(undefined as string | undefined); @@ -199,7 +200,7 @@ export class DataTable extends MosaicClient { * A Mosiac lifecycle function called with arrow results from `query`. * Must be synchronous, and return `this`. */ - queryResult(table: arrow.Table): this { + queryResult(table: flech.Table): this { if (!this.#pendingInternalRequest) { // data is not from an internal request, so reset table this.#reader = new AsyncBatchReader(() => { @@ -362,7 +363,7 @@ export class DataTable extends MosaicClient { } } - #appendRow(d: arrow.StructRowProxy, i: number) { + #appendRow(d: TableRow, i: number) { let itr = this.#templateRow?.cloneNode(true); assert(itr, "Must have a data row"); let td = itr.childNodes[0] as HTMLTableCellElement; @@ -389,7 +390,7 @@ const TRUNCATE = /** @type {const} */ ({ }); function thcol( - field: arrow.Field, + field: flech.Field, minWidth: number, vis?: ColumnSummaryClient, ) { @@ -502,7 +503,7 @@ function thcol( /** * Return a formatter for each field in the schema */ -function formatof(schema: arrow.Schema) { +function formatof(schema: flech.Schema) { const format: Record string> = Object.create( null, ); @@ -515,20 +516,20 @@ function formatof(schema: arrow.Schema) { /** * Return a class type of each field in the schema. */ -function classof(schema: arrow.Schema): Record { +function classof(schema: flech.Schema): Record { const classes: Record = Object.create(null); for (const field of schema.fields) { - if ( - arrow.DataType.isInt(field.type) || - arrow.DataType.isFloat(field.type) - ) { - classes[field.name] = "number"; - } - if ( - arrow.DataType.isDate(field.type) || - arrow.DataType.isTimestamp(field.type) - ) { - classes[field.name] = "date"; + switch (field.type.typeId) { + case flech.Type.Int: + case flech.Type.Float: + classes[field.name] = "number"; + break; + case flech.Type.Date: + case flech.Type.Timestamp: + classes[field.name] = "date"; + break; + default: + break; } } return classes; diff --git a/lib/clients/Histogram.ts b/lib/clients/Histogram.ts index c07a749..2bdd277 100644 --- a/lib/clients/Histogram.ts +++ b/lib/clients/Histogram.ts @@ -9,7 +9,7 @@ import { // @ts-types="../deps/mosaic-sql.d.ts"; import { count, Query, type SQLExpression } from "@uwdata/mosaic-sql"; import * as mplot from "@uwdata/mosaic-plot"; -import type * as arrow from "apache-arrow"; +import type * as flech from "@uwdata/flechette"; import { CrossfilterHistogramPlot } from "../utils/CrossfilterHistogramPlot.ts"; @@ -21,7 +21,7 @@ interface HistogramOptions { /** The table to query. */ table: string; /** An arrow Field containing the column info to use for the histogram. */ - field: arrow.Field; + field: flech.Field; /** The column to use for the histogram. */ column: string; /** The type of the column. Must be "number" or "date". */ @@ -30,14 +30,14 @@ interface HistogramOptions { filterBy: Selection; } -type BinTable = arrow.Table<{ x1: arrow.Int; x2: arrow.Int; y: arrow.Int }>; +type BinTable = flech.Table; /** Represents a Cross-filtered Histogram */ export class Histogram extends MosaicClient implements Mark { #source: { table: string; column: string; - field: arrow.Field; + field: flech.Field; type: "number" | "date"; }; #el: HTMLElement = document.createElement("div"); @@ -102,11 +102,9 @@ export class Histogram extends MosaicClient implements Mark { * Provide query result data to the mark. */ queryResult(data: BinTable) { - let bins = Array.from(data, (d) => ({ - x0: d.x1, - x1: d.x2, - length: d.y, - })); + let bins: Array<{ x0: number; x1: number; length: number }> = data + .toArray() + .map((d) => ({ x0: d.x1, x1: d.x2, length: d.y })); let nullCount = 0; let nullBinIndex = bins.findIndex((b) => b.x0 == null); if (nullBinIndex >= 0) { diff --git a/lib/clients/StatusBar.ts b/lib/clients/StatusBar.ts index 2477dc6..7befb56 100644 --- a/lib/clients/StatusBar.ts +++ b/lib/clients/StatusBar.ts @@ -1,4 +1,4 @@ -import type * as arrow from "apache-arrow"; +import * as flech from "@uwdata/flechette"; // @ts-types="../deps/mosaic-core.d.ts" import { type Interactor, @@ -7,6 +7,7 @@ import { } from "@uwdata/mosaic-core"; // @ts-types="../deps/mosaic-sql.d.ts" import { count, Query } from "@uwdata/mosaic-sql"; +import { assert } from "../utils/assert.ts"; interface StatusBarOptions { table: string; @@ -66,7 +67,13 @@ export class StatusBar extends MosaicClient { return query; } - queryResult(table: arrow.Table<{ count: arrow.Int }>) { + queryResult(table: flech.Table) { + assert( + table.schema.fields.find((f) => f.name === "count")?.type.typeId === + flech.Type.Int, + "Expected count field to be an integer", + ); + let count = Number(table.get(0)?.count ?? 0); if (!this.#totalRows) { // we need to know the total number of rows to display diff --git a/lib/clients/ValueCounts.ts b/lib/clients/ValueCounts.ts index 7eaaf43..a18b233 100644 --- a/lib/clients/ValueCounts.ts +++ b/lib/clients/ValueCounts.ts @@ -9,7 +9,7 @@ import { type SQLExpression, sum, } from "@uwdata/mosaic-sql"; -import type * as arrow from "apache-arrow"; +import type * as flech from "@uwdata/flechette"; import { effect } from "@preact/signals-core"; import { ValueCountsPlot } from "../utils/ValueCountsPlot.ts"; @@ -19,17 +19,17 @@ interface UniqueValuesOptions { /** The table to query. */ table: string; /** An arrow Field containing the column info to use for the histogram. */ - field: arrow.Field; + field: flech.Field; /** A mosaic selection to filter the data. */ filterBy: Selection; } -type CountTable = arrow.Table<{ key: arrow.Utf8; total: arrow.Int }>; +type CountTable = flech.Table; export class ValueCounts extends MosaicClient { #table: string; #column: string; - #field: arrow.Field; + #field: flech.Field; #el: HTMLElement = document.createElement("div"); #plot: ReturnType | undefined; diff --git a/lib/demo.ts b/lib/demo.ts index e54aa39..8cd8b7d 100644 --- a/lib/demo.ts +++ b/lib/demo.ts @@ -1,4 +1,5 @@ /// +// @ts-types="./deps/mosaic-core.d.ts"; import * as mc from "@uwdata/mosaic-core"; import * as msql from "@uwdata/mosaic-sql"; diff --git a/lib/deps/mosaic-core.d.ts b/lib/deps/mosaic-core.d.ts index 4f936f3..2cfadc8 100644 --- a/lib/deps/mosaic-core.d.ts +++ b/lib/deps/mosaic-core.d.ts @@ -1,6 +1,6 @@ // @ts-types="./mosaic-sql.d.ts"; import type { Query, SQLExpression } from "@uwdata/mosaic-sql"; -import type * as arrow from "apache-arrow"; +import type * as flechette from "@uwdata/flechette"; export interface Interactor { reset(): void; @@ -113,7 +113,7 @@ export class MosaicClient { query(filter?: Array): Query; /** Called before the coordinator submitting a query to inform the client */ queryPending(): this; - queryResult(data: arrow.Table): this; + queryResult(data: flechette.Table): this; queryError(error: unknown): this; requestQuery(query: Query): void; requestUpdate(query: Query): void; @@ -124,7 +124,7 @@ export type ConnectorQuery = { type: "arrow" | "json"; sql: string }; export interface Connector { query( query: ConnectorQuery, - ): Promise>; + ): Promise>; } export class Coordinator { @@ -134,8 +134,11 @@ export class Coordinator { prefetch(query: Query): void; logger(impl?: unknown): Logger; databaseConnector(connector: Connector): void; - query(query: Query): Promise; + query(query: Query): Promise; clear(): void; + dataCubeIndexer: { + schema: string | undefined; + }; } type Logger = typeof console & { diff --git a/lib/utils/AsyncBatchReader.ts b/lib/utils/AsyncBatchReader.ts index e5915de..16b02ee 100644 --- a/lib/utils/AsyncBatchReader.ts +++ b/lib/utils/AsyncBatchReader.ts @@ -22,13 +22,13 @@ import { assert } from "./assert.ts"; */ export class AsyncBatchReader { /** the iterable batches to read */ - #batches: Array<{ data: Iterator; last: boolean }> = []; + #batches: Array<{ data: Generator; last: boolean }> = []; /** the index of the current row */ #index: number = 0; /** resolves a promise for when the next batch is available */ #resolve: (() => void) | null = null; /** the current batch */ - #current: { data: Iterator; last: boolean } | null = null; + #current: { data: Generator; last: boolean } | null = null; /** A function to request more data. */ #requestNextBatch: () => void; /** @@ -50,7 +50,7 @@ export class AsyncBatchReader { * @param options * @param options.last - whether this is the last batch */ - enqueueBatch(batch: Iterator, { last }: { last: boolean }) { + enqueueBatch(batch: Generator, { last }: { last: boolean }) { this.#batches.push({ data: batch, last }); if (this.#resolve) { this.#resolve(); diff --git a/lib/utils/CrossfilterHistogramPlot.ts b/lib/utils/CrossfilterHistogramPlot.ts index 8ddec70..ac5df18 100644 --- a/lib/utils/CrossfilterHistogramPlot.ts +++ b/lib/utils/CrossfilterHistogramPlot.ts @@ -4,7 +4,7 @@ import * as d3 from "d3"; import { assert } from "../utils/assert.ts"; import { tickFormatterForBins } from "./tick-formatter-for-bins.ts"; import type { Bin, Scale } from "../types.ts"; -import type * as arrow from "apache-arrow"; +import type * as flech from "@uwdata/flechette"; import { formatDataType, percentFormatter } from "./formatting.ts"; interface HistogramOptions { @@ -29,7 +29,7 @@ interface HistogramOptions { */ export function CrossfilterHistogramPlot( bins: Array, - field: arrow.Field, + field: flech.Field, { type = "number", width = 125, diff --git a/lib/utils/ValueCountsPlot.ts b/lib/utils/ValueCountsPlot.ts index 67fd8d6..c4fa5f4 100644 --- a/lib/utils/ValueCountsPlot.ts +++ b/lib/utils/ValueCountsPlot.ts @@ -1,14 +1,11 @@ import { effect, signal } from "@preact/signals-core"; -import type * as arrow from "apache-arrow"; +import type * as flech from "@uwdata/flechette"; // @ts-types="npm:@types/d3" import * as d3 from "d3"; import { assert } from "./assert.ts"; import { formatDataType, percentFormatter } from "./formatting.ts"; -type CountTableData = arrow.Table<{ - key: arrow.Utf8; - total: arrow.Int; -}>; +type CountTableData = flech.Table; interface ValueCountsPlot { width?: number; @@ -24,7 +21,7 @@ interface ValueCountsPlot { export function ValueCountsPlot( data: CountTableData, - field: arrow.Field, + field: flech.Field, { width = 125, height = 30, @@ -178,9 +175,11 @@ function createBar(opts: { } function prepareData(data: CountTableData) { - let arr: Array<{ key: string; total: number }> = data + let arr = data .toArray() - .toSorted((a, b) => b.total - a.total); + .toSorted((a, b) => b.total - a.total) as Array< + { key: string; total: number } + >; let total = arr.reduce((acc, d) => acc + d.total, 0); return { bins: arr.filter((d) => diff --git a/lib/utils/formatting.ts b/lib/utils/formatting.ts index 1026209..1db2977 100644 --- a/lib/utils/formatting.ts +++ b/lib/utils/formatting.ts @@ -1,5 +1,5 @@ import { Temporal } from "@js-temporal/polyfill"; -import * as arrow from "apache-arrow"; +import * as flech from "@uwdata/flechette"; import { format } from "d3"; /** @@ -9,12 +9,9 @@ import { format } from "d3"; * correctly typed. */ function fmt( - _arrowDataTypeValue: TValue, format: (value: TValue) => string, - log = false, ): (value: TValue | null | undefined) => string { return (value) => { - if (log) console.log(value); if (value === undefined || value === null) { return stringify(value); } @@ -26,203 +23,290 @@ function stringify(x: unknown): string { return `${x}`; } -/** @param {arrow.DataType} type */ -export function formatDataType(type: arrow.DataType) { - // special case some types - if (arrow.DataType.isLargeBinary(type)) return "large binary"; - if (arrow.DataType.isLargeUtf8(type)) return "large utf8"; - // otherwise, just stringify and lowercase - return type - .toString() - .toLowerCase() - .replace("", "[s]") - .replace("", "[ms]") - .replace("", "[µs]") - .replace("", "[ns]") - .replace("", "[day]") - .replace("dictionary<", "dict<"); +/** @param {flech.DataType} type */ +export function formatDataType(type: flech.DataType): string { + switch (type.typeId) { + case flech.Type.Dictionary: { + let inner = formatDataType(type.dictionary); + return `dict<${inner}>`; + } + case flech.Type.NONE: + return "none"; + case flech.Type.Null: + return "null"; + case flech.Type.Int: + return `${type.signed ? "int" : "uint"}${type.bitWidth}`; + case flech.Type.Float: { + let precision = { + [flech.Precision.HALF]: "16", + [flech.Precision.SINGLE]: "32", + [flech.Precision.DOUBLE]: "64", + }[type.precision]; + return `float${precision}`; + } + case flech.Type.Binary: + return "binary"; + case flech.Type.Utf8: + return "utf8"; + case flech.Type.Bool: + return "bool"; + case flech.Type.Decimal: + return `decimal(${type.precision}, ${type.scale})`; + case flech.Type.Date: { + let unit = { + [flech.DateUnit.DAY]: "day", + [flech.DateUnit.MILLISECOND]: "ms", + }[type.unit]; + return `date[${unit}]`; + } + case flech.Type.Time: { + let unit = { + [flech.TimeUnit.SECOND]: "s", + [flech.TimeUnit.MILLISECOND]: "ms", + [flech.TimeUnit.MICROSECOND]: "µs", + [flech.TimeUnit.NANOSECOND]: "ns", + }[type.unit]; + let bitWidth = type.bitWidth; + return `time${bitWidth}[${unit}]`; + } + case flech.Type.Timestamp: + return type.timezone ? `timestamp[tz=${type.timezone}]` : "timestamp"; + case flech.Type.Interval: { + let unit = { + [flech.IntervalUnit.YEAR_MONTH]: "ym", + [flech.IntervalUnit.DAY_TIME]: "dt", + [flech.IntervalUnit.MONTH_DAY_NANO]: "mdn", + }[type.unit]; + return `interval[${unit}]`; + } + case flech.Type.List: { + let inner = formatDataType(type.children[0].type); + return `list[${inner}]`; + } + case flech.Type.Struct: { + let fields = type.children.map((field) => { + return `${field.name}: ${formatDataType(field.type)}`; + }); + return `struct<${fields.join(", ")}>`; + } + case flech.Type.Union: { + let mode = { + [flech.UnionMode.Sparse]: "sparse", + [flech.UnionMode.Dense]: "dense", + }[type.mode]; + let fields = type.children.map((field) => { + return `${field.name}: ${formatDataType(field.type)}`; + }); + return `union[${fields.join(", ")}]`; + } + case flech.Type.FixedSizeBinary: + return `binary[stride=${type.stride}]`; + case flech.Type.FixedSizeList: { + let inner = formatDataType(type.children[0].type); + return `list[${inner}]`; + } + case flech.Type.Map: { + let values = formatDataType(type.children[0].type); + return `map<${values}>`; + } + case flech.Type.Duration: { + let unit = { + [flech.TimeUnit.SECOND]: "s", + [flech.TimeUnit.MILLISECOND]: "ms", + [flech.TimeUnit.MICROSECOND]: "µs", + [flech.TimeUnit.NANOSECOND]: "ns", + }[type.unit]; + return `duration[${unit}]`; + } + case flech.Type.LargeBinary: + return `large binary`; + case flech.Type.LargeUtf8: + return `large utf8`; + case flech.Type.LargeList: + return `large list`; + case flech.Type.RunEndEncoded: { + let values = formatDataType(type.children[0].type); + let index = formatDataType(type.children[1].type); + return `ree<${values}, ${index}>`; + } + case flech.Type.BinaryView: + return `binary view`; + case flech.Type.Utf8View: + return `utf8 view`; + case flech.Type.ListView: + return `list view`; + case flech.Type.LargeListView: + return `large list view`; + } } /** - * @param {arrow.DataType} type + * @param {flech.DataType} type * @returns {(value: any) => string} + * + * @see https://idl.uw.edu/flechette/api/data-types#int */ export function formatterForValue( - type: arrow.DataType, + type: flech.DataType, // deno-lint-ignore no-explicit-any ): (value: any) => string { - if (arrow.DataType.isNull(type)) { - return fmt(type.TValue, stringify); - } - - if ( - arrow.DataType.isInt(type) || - arrow.DataType.isFloat(type) - ) { - return fmt(type.TValue, (value) => { - if (Number.isNaN(value)) return "NaN"; - return value === 0 ? "0" : value.toLocaleString("en"); // handle negative zero - }); - } - - if ( - arrow.DataType.isBinary(type) || - arrow.DataType.isFixedSizeBinary(type) || - arrow.DataType.isLargeBinary(type) - ) { - return fmt(type.TValue, (bytes) => { - let maxlen = 32; - let result = "b'"; - for (let i = 0; i < Math.min(bytes.length, maxlen); i++) { - const byte = bytes[i]; - if (byte >= 32 && byte <= 126) { - // ASCII printable characters range from 32 (space) to 126 (~) - result += String.fromCharCode(byte); - } else { - result += "\\x" + ("00" + byte.toString(16)).slice(-2); + switch (type.typeId) { + case flech.Type.NONE: + return fmt(stringify); + case flech.Type.Null: + return fmt(stringify); + case flech.Type.Int: + case flech.Type.Float: + return fmt((value) => { + if (Number.isNaN(value)) return "NaN"; + return value === 0 ? "0" : value.toLocaleString("en"); // handle negative zero + }); + case flech.Type.Binary: + case flech.Type.BinaryView: + case flech.Type.FixedSizeBinary: + case flech.Type.LargeBinary: + return fmt((bytes) => { + let maxlen = 32; + let result = "b'"; + for (let i = 0; i < Math.min(bytes.length, maxlen); i++) { + const byte = bytes[i]; + if (byte >= 32 && byte <= 126) { + // ASCII printable characters range from 32 (space) to 126 (~) + result += String.fromCharCode(byte); + } else { + result += "\\x" + ("00" + byte.toString(16)).slice(-2); + } } - } - if (bytes.length > maxlen) result += "..."; - result += "'"; - return result; - }); - } - - if (arrow.DataType.isUtf8(type) || arrow.DataType.isLargeUtf8(type)) { - return fmt(type.TValue, (text) => text); - } - - if (arrow.DataType.isBool(type)) { - return fmt(type.TValue, stringify); - } - - if (arrow.DataType.isDecimal(type)) { - return fmt(type.TValue, () => "TODO"); - } - - if (arrow.DataType.isDate(type)) { - return fmt(type.TValue, (ms) => { - // Always returns value in milliseconds - // https://github.com/apache/arrow/blob/89d6354068c11a66fcec2f34d0414daca327e2e0/js/src/visitor/get.ts#L167-L171 - return Temporal.Instant - .fromEpochMilliseconds(ms) - .toZonedDateTimeISO("UTC") - .toPlainDate() - .toString(); - }); - } - - if (arrow.DataType.isTime(type)) { - return fmt(type.TValue, (ms) => { - return instantFromTimeUnit(ms, type.unit) - .toZonedDateTimeISO("UTC") - .toPlainTime() - .toString(); - }); - } - - if (arrow.DataType.isTimestamp(type)) { - return fmt(type.TValue, (ms) => { - // Always returns value in milliseconds - // https://github.com/apache/arrow/blob/89d6354068c11a66fcec2f34d0414daca327e2e0/js/src/visitor/get.ts#L173-L190 - return Temporal.Instant - .fromEpochMilliseconds(ms) - .toZonedDateTimeISO("UTC") - .toPlainDateTime() - .toString(); - }); - } - - if (arrow.DataType.isInterval(type)) { - return fmt(type.TValue, (_value) => { - return "TODO"; - }); - } - - if (arrow.DataType.isDuration(type)) { - return fmt(type.TValue, (bigintValue) => { - // https://tc39.es/proposal-temporal/docs/duration.html#toString - return durationFromTimeUnit(bigintValue, type.unit).toString(); - }); - } - - if (arrow.DataType.isList(type)) { - return fmt(type.TValue, (value) => { - // TODO: Some recursive formatting? - return value.toString(); - }); - } - - if (arrow.DataType.isStruct(type)) { - return fmt(type.TValue, (value) => { - // TODO: Some recursive formatting? - return value.toString(); - }); - } - - if (arrow.DataType.isUnion(type)) { - return fmt(type.TValue, (_value) => { - return "TODO"; - }); - } - if (arrow.DataType.isMap(type)) { - return fmt(type.TValue, (_value) => { - return "TODO"; - }); - } - - if (arrow.DataType.isDictionary(type)) { - let formatter = formatterForValue(type.dictionary); - return fmt(type.TValue, formatter); + if (bytes.length > maxlen) result += "..."; + result += "'"; + return result; + }); + case flech.Type.Utf8: + case flech.Type.Utf8View: + case flech.Type.LargeUtf8: + return fmt((s) => s); + case flech.Type.Bool: + return fmt(stringify); + case flech.Type.Decimal: + return fmt(() => "TODO"); + case flech.Type.Date: + return fmt((date) => { + return Temporal.Instant + .fromEpochMilliseconds( + typeof date === "number" ? date : date.getTime(), + ) + .toZonedDateTimeISO("UTC") + .toPlainDate() + .toString(); + }); + case flech.Type.Time: + return fmt((ms) => { + return instantFromTimeUnit(ms, type.unit) + .toZonedDateTimeISO("UTC") + .toPlainTime() + .toString(); + }); + case flech.Type.Timestamp: + return fmt((date) => { + return Temporal.Instant + .fromEpochMilliseconds( + typeof date === "number" ? date : date.getTime(), + ) + .toZonedDateTimeISO("UTC") + .toPlainDateTime() + .toString(); + }); + case flech.Type.Interval: + return fmt(() => "TODO"); + case flech.Type.List: + case flech.Type.LargeList: + case flech.Type.ListView: + case flech.Type.FixedSizeList: + case flech.Type.LargeListView: { + let maxItems = 5; + return fmt | flech.TypedArray>((value) => { + let items = Array.from(value.slice(0, maxItems)); + if (value.length > maxItems) items.push("..."); + return `[${items.join(", ")}]`; + }); + } + case flech.Type.Duration: + return fmt((bigintValue) => { + // https://tc39.es/proposal-temporal/docs/duration.html#toString + return durationFromTimeUnit(bigintValue, type.unit).toString(); + }); + case flech.Type.Struct: + return fmt>((value) => { + // TODO: Some recursive formatting? + return value.toString(); + }); + case flech.Type.Union: + return fmt(() => "TODO"); + case flech.Type.Map: { + return fmt>((value) => { + let obj = Object.fromEntries( + value.map(([key, value]) => [key, value]), + ); + return JSON.stringify(obj); + }); + } + case flech.Type.RunEndEncoded: { + return fmt(() => "TODO"); + } + case flech.Type.Dictionary: { + // TODO: some recursive formatting? + return fmt(stringify); + } } - - return () => `Unsupported type: ${type}`; } +type TimeUnit = + | typeof flech.TimeUnit.SECOND + | typeof flech.TimeUnit.MILLISECOND + | typeof flech.TimeUnit.MICROSECOND + | typeof flech.TimeUnit.NANOSECOND; + /** * @param {number | bigint} value - * @param {arrow.TimeUnit} unit + * @param {flech.TimeUnit} unit */ -function instantFromTimeUnit(value: number | bigint, unit: arrow.TimeUnit) { - if (unit === arrow.TimeUnit.SECOND) { - if (typeof value === "bigint") value = Number(value); - return Temporal.Instant.fromEpochSeconds(value); +function instantFromTimeUnit( + value: number | bigint, + unit: TimeUnit, +) { + switch (unit) { + case flech.TimeUnit.SECOND: + if (typeof value === "bigint") value = Number(value); + return Temporal.Instant.fromEpochSeconds(value); + case flech.TimeUnit.MILLISECOND: + if (typeof value === "bigint") value = Number(value); + return Temporal.Instant.fromEpochMilliseconds(value); + case flech.TimeUnit.MICROSECOND: + if (typeof value === "number") value = BigInt(value); + return Temporal.Instant.fromEpochMicroseconds(value); + case flech.TimeUnit.NANOSECOND: + if (typeof value === "number") value = BigInt(value); + return Temporal.Instant.fromEpochNanoseconds(value); } - if (unit === arrow.TimeUnit.MILLISECOND) { - if (typeof value === "bigint") value = Number(value); - return Temporal.Instant.fromEpochMilliseconds(value); - } - if (unit === arrow.TimeUnit.MICROSECOND) { - if (typeof value === "number") value = BigInt(value); - return Temporal.Instant.fromEpochMicroseconds(value); - } - if (unit === arrow.TimeUnit.NANOSECOND) { - if (typeof value === "number") value = BigInt(value); - return Temporal.Instant.fromEpochNanoseconds(value); - } - throw new Error("Invalid TimeUnit"); } /** * @param {number | bigint} value - * @param {arrow.TimeUnit} unit + * @param {flech.TimeUnit} unit */ -function durationFromTimeUnit(value: number | bigint, unit: arrow.TimeUnit) { +function durationFromTimeUnit(value: number | bigint, unit: TimeUnit) { // TODO: Temporal.Duration polyfill only supports number not bigint value = Number(value); - if (unit === arrow.TimeUnit.SECOND) { - return Temporal.Duration.from({ seconds: value }); - } - if (unit === arrow.TimeUnit.MILLISECOND) { - return Temporal.Duration.from({ milliseconds: value }); - } - if (unit === arrow.TimeUnit.MICROSECOND) { - return Temporal.Duration.from({ microseconds: value }); - } - if (unit === arrow.TimeUnit.NANOSECOND) { - return Temporal.Duration.from({ nanoseconds: value }); + switch (unit) { + case flech.TimeUnit.SECOND: + return Temporal.Duration.from({ seconds: value }); + case flech.TimeUnit.MILLISECOND: + return Temporal.Duration.from({ milliseconds: value }); + case flech.TimeUnit.MICROSECOND: + return Temporal.Duration.from({ microseconds: value }); + case flech.TimeUnit.NANOSECOND: + return Temporal.Duration.from({ nanoseconds: value }); } - throw new Error("Invalid TimeUnit"); } /** diff --git a/lib/widget.ts b/lib/widget.ts index 4f8deb3..c04bbb2 100644 --- a/lib/widget.ts +++ b/lib/widget.ts @@ -2,7 +2,7 @@ import * as mc from "@uwdata/mosaic-core"; // @ts-types="./deps/mosaic-sql.d.ts"; import { Query } from "@uwdata/mosaic-sql"; -import * as arrow from "apache-arrow"; +import * as flech from "@uwdata/flechette"; import * as uuid from "@lukeed/uuid"; import { DataTable } from "./clients/DataTable.ts"; @@ -12,26 +12,29 @@ import { defer } from "./utils/defer.ts"; type Model = { _table_name: string; _columns: Array; - temp_indexes: boolean; + data_cube_schema: string; sql: string; }; interface OpenQuery { query: mc.ConnectorQuery; startTime: number; - resolve: (x: arrow.Table | Record) => void; + resolve: (x: flech.Table | Record) => void; reject: (err?: string) => void; } export default () => { let coordinator = new mc.Coordinator(); - let schema: arrow.Schema; + let schema: flech.Schema; return { async initialize( - { model }: import("npm:@anywidget/types").InitializeProps, + { model }: import("npm:@anywidget/types@0.2.0").InitializeProps< + Model + >, ) { let logger = coordinator.logger(_voidLogger()); + let getDataCubeSchema = () => model.get("data_cube_schema"); let openQueries = new Map(); /** @@ -41,7 +44,7 @@ export default () => { */ function send( query: mc.ConnectorQuery, - resolve: (value: arrow.Table | Record) => void, + resolve: (value: flech.Table | Record) => void, reject: (reason?: string) => void, ) { let id = uuid.v4(); @@ -71,7 +74,7 @@ export default () => { } else { switch (msg.type) { case "arrow": { - let table = arrow.tableFromIPC(buffers[0].buffer); + let table = flech.tableFromIPC(buffers[0].buffer); logger.log("table", table); query.resolve(table); break; @@ -93,29 +96,32 @@ export default () => { coordinator.databaseConnector({ query(query) { let { promise, resolve, reject } = defer< - arrow.Table | Record, + flech.Table | Record, string >(); send(query, resolve, reject); return promise; }, }); + coordinator.dataCubeIndexer.schema = getDataCubeSchema(); + model.on("change:data_cube_schema", () => { + coordinator.dataCubeIndexer.schema = getDataCubeSchema(); + }); - // get some initial data to get the schema - let empty = await coordinator.query( - Query - .from(model.get("_table_name")) - .select(...model.get("_columns")) - .limit(0) - .toString(), - ); - schema = empty.schema; + schema = await getTableSchema(coordinator, { + tableName: model.get("_table_name"), + columns: model.get("_columns"), + }); return () => { coordinator.clear(); }; }, - render({ model, el }: import("npm:@anywidget/types").RenderProps) { + render( + { model, el }: import("npm:@anywidget/types@0.2.0").RenderProps< + Model + >, + ) { let table = new DataTable({ table: model.get("_table_name"), schema: schema, @@ -130,6 +136,23 @@ export default () => { }; }; +async function getTableSchema( + coordinator: mc.Coordinator, + options: { + tableName: string; + columns: Array; + }, +) { + let empty = await coordinator.query( + Query + .from(options.tableName) + .select(...options.columns) + .limit(0) + .toString(), + ); + return empty.schema; +} + function _voidLogger() { return Object.fromEntries( Object.keys(console).map((key) => [key, () => {}]), diff --git a/src/quak/_widget.py b/src/quak/_widget.py index 75f111f..81fefb7 100644 --- a/src/quak/_widget.py +++ b/src/quak/_widget.py @@ -32,9 +32,12 @@ class Widget(anywidget.AnyWidget): _esm = pathlib.Path(__file__).parent / "widget.js" _table_name = traitlets.Unicode().tag(sync=True) _columns = traitlets.List(traitlets.Unicode()).tag(sync=True) + + # The SQL query for the current data (read-only) sql = traitlets.Unicode().tag(sync=True) - # Whether data cube indexes should be created as temp tables - temp_indexes = traitlets.Bool().tag(sync=True) + + # Where data cube indexes should be created + data_cube_schema = traitlets.Unicode("quak").tag(sync=True) def __init__(self, data, *, table: str = "df"): if isinstance(data, duckdb.DuckDBPyConnection): @@ -68,7 +71,6 @@ def __init__(self, data, *, table: str = "df"): super().__init__( _table_name=table, _columns=get_columns(conn, table), - temp_indexes=True, sql=f'SELECT * FROM "{table}"', ) self.on_msg(self._handle_custom_msg) diff --git a/uv.lock b/uv.lock index 37aaf4d..561ce99 100644 --- a/uv.lock +++ b/uv.lock @@ -1598,7 +1598,7 @@ wheels = [ [[package]] name = "quak" -version = "0.1.8" +version = "0.1.9" source = { editable = "." } dependencies = [ { name = "anywidget" },