From 312c4d39ac5eb1c6c75e8ecee1c4bc89ed799675 Mon Sep 17 00:00:00 2001 From: FabianLars Date: Wed, 3 May 2023 07:03:52 +0000 Subject: [PATCH] Rename dev branch to v1 and next branch to v2 Committed via a GitHub action: https://github.com/tauri-apps/plugins-workspace/actions/runs/4869194441 Co-authored-by: FabianLars --- .gitignore | 1 + Cargo.toml | 26 +++ LICENSE.spdx | 20 +++ LICENSE_APACHE-2.0 | 177 +++++++++++++++++++++ LICENSE_MIT | 21 +++ README.md | 76 +++++++++ banner.png | Bin 0 -> 40282 bytes dist-js/index.d.ts | 95 +++++++++++ dist-js/index.min.js | 119 ++++++++++++++ dist-js/index.min.js.map | 1 + dist-js/index.mjs | 117 ++++++++++++++ dist-js/index.mjs.map | 1 + guest-js/index.ts | 140 ++++++++++++++++ package.json | 33 ++++ rollup.config.mjs | 11 ++ src/decode/mod.rs | 15 ++ src/decode/mysql.rs | 90 +++++++++++ src/decode/postgres.rs | 82 ++++++++++ src/decode/sqlite.rs | 74 +++++++++ src/lib.rs | 19 +++ src/plugin.rs | 336 +++++++++++++++++++++++++++++++++++++++ tsconfig.json | 1 + 22 files changed, 1455 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE.spdx create mode 100644 LICENSE_APACHE-2.0 create mode 100644 LICENSE_MIT create mode 100644 README.md create mode 100644 banner.png create mode 100644 dist-js/index.d.ts create mode 100644 dist-js/index.min.js create mode 100644 dist-js/index.min.js.map create mode 100644 dist-js/index.mjs create mode 100644 dist-js/index.mjs.map create mode 100644 guest-js/index.ts create mode 100644 package.json create mode 100644 rollup.config.mjs create mode 100644 src/decode/mod.rs create mode 100644 src/decode/mysql.rs create mode 100644 src/decode/postgres.rs create mode 100644 src/decode/sqlite.rs create mode 100644 src/lib.rs create mode 100644 src/plugin.rs create mode 120000 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..91a839a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "tauri-plugin-sql" +version = "0.0.0" +description = "Interface with SQL databases." +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde.workspace = true +serde_json.workspace = true +tauri.workspace = true +log.workspace = true +thiserror.workspace = true +futures-core = "0.3" +sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json", "time"] } +time = "0.3" +tokio = { version = "1", features = ["sync"] } + +[features] +sqlite = ["sqlx/sqlite"] +mysql = ["sqlx/mysql"] +postgres = ["sqlx/postgres"] \ No newline at end of file diff --git a/LICENSE.spdx b/LICENSE.spdx new file mode 100644 index 0000000..cdd0df5 --- /dev/null +++ b/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/LICENSE_APACHE-2.0 b/LICENSE_APACHE-2.0 new file mode 100644 index 0000000..4947287 --- /dev/null +++ b/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/LICENSE_MIT b/LICENSE_MIT new file mode 100644 index 0000000..4d75472 --- /dev/null +++ b/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2522848 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +![plugin-sql](banner.png) + +Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx). It supports the `sqlite`, `mysql` and `postgres` drivers, enabled by a Cargo feature. + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies.tauri-plugin-sql] +git = "https://github.com/tauri-apps/plugins-workspace" +branch = "v1" +features = ["sqlite"] # or "postgres", or "mysql" +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use. + +```sh +pnpm add https://github.com/tauri-apps/tauri-plugin-sql +# or +npm add https://github.com/tauri-apps/tauri-plugin-sql +# or +yarn add https://github.com/tauri-apps/tauri-plugin-sql +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/main.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_sql::Builder::default().build()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import Database from "tauri-plugin-sql-api"; + +// sqlite. The path is relative to `tauri::api::path::BaseDirectory::App`. +const db = await Database.load("sqlite:test.db"); +// mysql +const db = await Database.load("mysql://user:pass@host/database"); +// postgres +const db = await Database.load("postgres://postgres:password@localhost/test"); + +await db.execute("INSERT INTO ..."); +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/banner.png b/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..5e53a31b40da2334134098b907c703dc65a48d29 GIT binary patch literal 40282 zcmXtg2{_d4_x@)PO_quZQ4+?I7^BF(CEM8d>@oH&OCrly(p$X9*v76<$dWZnO!Qi2 zi0r${zL$OXf2QyM=eoMO(s(|f=RD`!=RWs2kC!@HD)i^Bo`WEWUQPAB9t52wK@hC$ zEH(Jf=R!{c_zUi-YU&L^v`pkbFeo*h1$-0ct*4?0m3(7g1plCNP|#F>ppUV%M>c05 zD1BY+zJkG1*vhfRKbZLpj)PM7`&&n5D4o~SKdD`i6q9z8IDl9 zw9sgFjhW{+&b;iTjlL6a^9vTmWrc2ieT{#<)t2_YP&5}jikrsODNZ=vx&vwK5R?D? zQeyt=QFYGLoaKH$l6F_0*>-u6bLr zcA4%n?zK-=N3GEdpP-dE-(`x5?nc)yUetzJuwxxw$O& z4AM!HnfBC3z)@`A_=a5S>$)7)m&IR-A|A=s`XZ0&+hVSfHqEMyBKx*?U4uTu?!6Lq z_-4+WFd|7fON8i5gnnsdN=vW?j&vNZH_1s{nc;$3a2M=iDDii?xVkH*XslDbCaqpf zXUtRjm?_36Bc?*v3TuqTb2qCUfyMmjP$m=8B@7~DQGN?Wm z8G@h|!@`%!JEtLkm_xopJr;k;pS*UIq68Ij=6=e`A2tY3BrU`>)Z zR@jehU_C7pqM<5tlo+wJu$VZJrsP>(US^nqWRjyQ#wAkKQBu$CDj!m0CoNITf6GXd z?tZM~%Zjz>2>x86m0d_DFV}zQ&Z)8>_OsfuAYAm+cG+K1^z`Fmj^%RJ^ixNd zFLL97ItMh?PF_F0VV_E19m?Pi`i5gmRD)slj?<5kA8wY9bT;|2QfuAgG6@Vt}$^BHn(0dJUX@Wl%GX4SHGu+xK& zwry9jLaNI6gC7}#8eu5t_>P7u!Y}sfB^qn-LJ~x$-x{pd-N_V>qDWWM>;?gu7P|B){wQh3Z)3T9U45{?x&x)I~d!2lYJJB$TRN@|31dtA#Rj z9RIR&rJEFCf1BCXxWpVUZJq-2ynvT4Zb8**A(7{Mtni--!na#Ppo6yzr1yf96Gp}d z)t?zuE~oBK6z}exEkJ1NCTpRSel=ll8%7{H_r?P)& zHzRT_K@EvG+=^lr){;F(ggkZNr2d~N2ct0Ti7wcD(_go!F}5vl!Hu_p=nk2XtU1}v zx0Td4MchuVzS8S$rY$!F`RFhVI&^0Op9tqp7P4@L78*YbbmS^Op#DrBNQpcTH&0ro z9>72c8iuUc;J!#W`NfAVXXqIm^K>C*lie~O=PPO&MlX212y z*18;~cTR|AxvJ_e+!z}}`mwXWIq&y@8yAP(ne_*VffOd6Sa=~gMMD~lB8GKA%m%N! zZ&ydh!_n(Q(7Q!?kD%Q8Z)s`Cmu7i;+n2@_%K%P0d~Cb1UKO1B^Eoc4QUQ2sus+!G zRHb!TFu!==3zzxFsxJ8eMve~#+fHrqhW1HDrgJkLxbp>Ixi5H8hZ2o9R*iupa4~tB z**5|(JT0>j8o~E29OcWys=x}3-m$RAcb`8ezgr+c+2ipLsn9fRuX5|T;mP4(?QTm~ zykJ9l=OD+kgV8FNSPrQtu1+C$2p>@Qyf$V!R=d4MepadlXbBeCT2bOk14!)NlHhk6EktvdDzXP|4kB;$l}<*QvLQ z>uIS+F^zUZ#Sh&2^GHna$dg66Yf?{EaP;zR#UDPH`|*BnZHN$yE>5xiap~jwSG_6ZZ<@SppujTVc zrDob^FxYRTFSSC6nO|Oyv!0U|pV8{xobT4AE(exj?QDGd=a*g6Z9Yl@YQW}D&w?7e zM)ZB~x%5fjC3!igMT%qH*ws->72)T`CoWz*U15UUf}5{n181^wr}_u`%&uZ%G;|QN zy(E|<&HOm5aEpzwkr;=j__xpR+Sv}M=WbpdTG#j%l>Kb!*k@;|kO*yc`^Z})o&&HW zmd}mcoT~Xo;&8u`y)EFF0$!x03kIVV9iZA8lX=p*k;Lzk6`gm zZba2VTgoN3^giAww%ly5df`HkU2CsWk38D8^L0jGGW)s#m50J=B@~syUs6`WyR+ov_DyIoj3%9SkJhTiL(hW6 zKN)e1F^*?3L{XHn%%4^)bz39s7CyF)yX zpk;~5(mATy&~w<}q9TJ&FxtA(N&7S7DFcJ)%^_&nJbXCU3rw|T`|Y%}w9^9E!fU$7 zeg!1~U$A1ELd)ZG+-cqRRuXkKuh;J`JGTP+ILhDT)03AX28+km zvAf%qmqplZ<({z?Tv8h`Pv%xVNBA`}n%jP2;>eMG%9!xI_YB!LxjxM%hvNIXF0zA# z@8>gH9BTaIHB-UQ^aeCksj(vJQu#OAIx6|e zFMatl6$XmTnXKXEvu%LaY2W3GS->@Q7O{`!EFZaB7)bj7F7g7Hxg&R#NF?se<8yr> z>Xarrd50{ssqaki4m;$-DMzSPsIA$+fk@?VvtOH99rf^eCVPyl#)e< z9uM5?!oiysqcW&24Gj*0cm0uEX-i@9VZ~v&dK`cYor*ypYJhGR_CR2J)E9#9v*taALe8FgZGF~z#^%ZK3o#I~I_;F?& znmS_{_7Z)-lklmr(LIC~5`AC9mf-9Lb;j;!KrLbxhOJ+ve6pN%I>!J}P@-{b^T{sj z92DNurk9V+{px;QnOaG25yG~Ro}&ex6ql4(GC$~)zfbWJPITmeK^3=FQ(jT&Z9+d5 z7}th^;nu@T#|>(J3oj1lH0JF))tuVyRNz}sX3UqH8n=If)TM1lA#R9y6=63nLFM_h z7TXc*`z}etiR1B~U;izSszOl3Siv>@YwBly{HU~|V4=AzkR9S$Q^~!Way@f9&EXs= z*Srh7=rnmHMRCLPuX6SDSHbJmF6O7Df`nydTzm`lad&6TLwo0=`ZxvQd;b7~m*(4R zOoZ>~So+$R=d7Q4O`2=h6+NMwNM5Zj5#$nVk>(lls?+LRx5)=v|Jo)ua^R^!*~3&2 zs5A8(pLB!x3C3=|lgDcP&2GZSsdIaKyW?cwKRT$h%||NVz$=ZRkOG+&9Xn*JG}3gH zje;dpk`K4(G;Pom)Hha~hQwD;VeAWqDm+XOhM50LH+D$w%NNdleh*_10syD2uW4U5 ztG82BfWYlMfe3-KTEle$_AzbS@;9MF(kGoDQKu3cgR2k=qS!n;WJ$haHA&1Bcx^&D zDR@d;1H%dqf3p4MA0J_7o+mH{#Ltv8kH%Vji9yPjSu#!BDe66edNOYrm$+&#AqaJK zbpaO7#i1w39mxj|9~|;vZHhcLP>T%5NDEWyhn@j06Qknu<@2fkDf{QvS7E50bXT)Bvm^M{tQV!Ya!=>|AcQ`6eD<=P6F0Xw|{ zTCgUS0aFUU*N1PE*a^*=5|X9?#VOR17!+&~s%t`hpkNZIJW^WM2~vU)> z7Jd%i-f+A#w$=*6GPFOgWyg6RX|-1|e4_aMWul`Nmfe%eX3f4Na(jS3n`0>(J^aKJ z`@-*#qduktoLg`SY~d4KWG=Js>o0Kzi7;zFR#jD1dr6&)m!Bf+s2E_`W+3dx-Na=$ z>EuP>j9N8w)Yf}TYJTED3UZELzP=$NR;8IQ{u+p_sl#P64tFO=vP4M9l3FR@A=R+y znqv3NLzy`C5s#A6Qth1_q9uj=?#x~~0sXj7Q~WNlxGUs{7`nH;t$7VeW&NpqJ7o#eGs$W~DL0W{~lT$)(_?H;7hyTidrVsP2{@ zaqzY>O2RxjgNEg8H2uKPJytd^H%cT!AJZD(Amp-vN7G5^M z=A9c#QnrCo;lxH!7CuqfVRwNu(GInkf0G&7QY!KHTGh|xaSIBhVZl*RFMrI5u&bP- zW{H)&=iYqnK#2{JG~;bH#E_MC@=Gfb1B@tno2d!EbsP25Y{~{spxG%zYyucqo~46i z5?r!S*(N3bZd|lRqdL5HRz_C4|D}gTYhOzeX+3kCL(inK6`SAmJLh`>V94Efp=)9j zQId%l4V0lhje0{|$JbP66x8D(5eWnukMjar+ANfG7CXzjnj^;p%PlsQTbNqZF+f?JjrGt=2Ra0BLe9ofeV5y=d#y=g^_f% z)ausxCLjVzF2f0NJU2|HE_p8Y^4KatT!3M!Irc_KZH0rw8ChI3LiZ(NPShxSbS3^7 zq$H5DWp3hG=$SE2O%-9o3R&OtiteU*{MsdNu}qNAGn4u*^G28Nx7OANl!Q-?po+~s z!*tEl;#U50GwLA{W|0K{fu>erc!LOr$(C8&W#tcoSn|i^b*z{*wQo|=b@=ch59Pp% zOmhoT9vi&CM4ML7k!eoZNuZQqh405d9S>*`*>6!d^o-S2J02J4Tdx@4V31dXwqw4D zg~hQB$5U*hhH8F^Sh_Q+#1CrapSD4a=VVpdyrFa-sVn)E7pP~Pv~Nz?qX4+!5dA-P z$gc{QSfmE*AneW66yirY91Xm)KRgZ!N^;2>=hm5-E2yKt22JZ3Cju=km5AzL>QfLG z|5|Z>tT<(_C7?01jOl>BfX4 zcj!W`YP5)jcL21#CB&bV-_N?GU9F-Cj*na-qY)7#J5TESy&$)JD3=>ANbLj#9)hw} z2nks?poaO7lyqu4ws2y?u?d{`iI&xRJOY!QRjK{<7FeMssvTW&M=MMAxFgUJlH@K< z?hPwYt)im5NigBLfy?gnDIn5aA(x{Wi4ft1fDqc)4qAv}+`A>NgK0`)dml{;i#Cg) z-5yRUquXlpN%S#!)7RA5<_ZjEN7=(E;fN5_&vkBow*Gktiq>T1*2*hB`1X5+x$j9w zHU#OUF?7y+8>?{W7K4I-YVeW@gD!-SK}>jC%B{Ao9a;^9Af=Bnwel10L>3yVd7kH95{!aV8A{j9>!)hRl8hxF)-aHL=M#%b2$$eoy%HucmOcv#LYfw)A z-Kkv~9v?qS18#12q2~Ifg7sHaEa3)mYQDvlmW3GXJ<9*$cOgPBYAkKdu=xXt=H=ejaF5)ev1s)^veW^>Bvdf;l2 zL%0^!_PhLa(H&ZTdPy(A>J(6lA>LXj6yLjKWKF%0iq4#oeBU@deD?xW6`eRRjVX*# z2tgYsJ`R_@=BaVw-7+ZO!7DBYM9l<=GwI_$1Y*M%1?Yzk zl|8m(>^7gR+!^`e5&_&}lr5%*W7|K|EjecR>UD`AjhiW;CgZj1spf9+F6*2@5?Jg! z?=hM9PR+JkB(UB{s-%HpZieE`T@tgS33NS|0iEkCyea>bA+EIJsXUEj^F2z)TOW~m2E56I1lW%oOGFe|9q!?H`2M@@ zJHsScl6&2;-dccQUNbCG&@FlC*lOkxrvhuJ0%Sc&e*bopo~5P2Y&u93EbDIOUViO> zYpjHb^6Af|@4HP;V8nRlrZ3og4HM2HeMaUnQ^r#lnUaOAGRbCj)F}9cCqCgsQVKo{ zLlyApPuU0p1;8@j4C!RbaI8y%DPJ88UVK&T`=@lO43yPFY*?NSf@@H=fhLTlZemoZ zxU^JbfD)qB74RPn#iysu1khz`0yD5jUP1R*6%u<4+hRFRPbYdHK{e_JM2qOx&gY1{ z6iP`!mY=7h&_;Q0qVLNm?w`x=5#8Ip>hQgq4~#g6X1*iwlJ)!5lNC zw{usy&@}PKb!a93x;9BVtmDZ-vLxT_l?QFQ;3HwG|L{! z0FlUg1Z9HXjPGTrNw4o8a)DG{->?Lt9ECvkYWTB5Jdq*j{ z{R2Tzn0ZiekotbbVf-FM_`sM@V73P*$v3?hP#S`hAlgI)M!*?4NwPwKZ#g%#XL}ZQ zIM!1^KBf{5{J0CV4TwZe$llQX`un1D+QwxRSW|S3wHJG6urBMX5`ArElN9z#%L+zd zjT|+3L=It5%5rdM$PgfZ0O!0QzUP~SW^M4TdGW$9ibJ(_cVFM_y83^_#l#GTseb4z zz1(5f#s(L7WEz`B3Na?ZuDlsHGynlWrzw+TU?=BbMFVZ=j;h!{syB#iZ!;g7U52E< zV-)ZKdfWqdSH+=w(d^nwzw-(&^$6Dn_eZ`gVFHWXq%6Xe<&GDeMN*B_@X(n|Eyf?4 z7j}I5hXy(j%$Z_L>5z8_`@;XcH1QnL?VUgxyu(qMmS8hr>n5>#=R3o(9u3CJV)1Ic zkmMv(i9Y19e(%*BvNm43EHvr;^8}Cypj$xUaWB`>#}ZsVQ9buQ(LWzZF`XoD;eLZp z0q=!N0FefK!QLjF{y+u8cOY*G@ykbH5z>i5p>ZtQq0W} zC%UGQvKNwI58oa$BJb1|@lZDR$>2n92wpo6MJt4f;LG?DxxKn-%FEx>U5{3+K3t(x zAIob`(FXPBd5I=Lbh*;$jPyqaLpG^*MG+XR5&6ivwEVQ@xaAaBnUo+xik)Jht*!0h zIA3h#Begh$6-^|+txN%*FGwP8A;kt4t5+$J+CbhCp+0b^JA)&J`y`gp^_(O7Vu!US z_48#ZUM8sD3xZgR=$-i*&Z(JEj>$7|%O;@|ww%i73ypYV=6jPfPL(4d8!xWcR*qVQ0S^eq(!g z@>wbDfPd(I0YYE<$xJGr`1#>#n4?C}_~VAq;KEy^+^d}A4Izzv5A)h5BA_7s(X;5} zD4!R1Ai_7sq>fs>=2k#+aGM3aZ}^#(3$;Q2VeB^Slj0Z;G-@>Z?25Zd_(?}fossj_ zbFhrK#LHr{L}2ADU9CB$gaYAC5JIUyRKw4}pkbwwQ`BqA8jN~kBRXJad5te(jr8?F z^;F&GC%Mj^VIELVend%Bx>Twa&_F%pFDK+IgF~yu{{;~FEISmhpVCmr?h0w88-nbO zM}=pveyqgQ0W~bcSfOpzKoc=7Rr{E5p9YH3d4VBI?1g3pwKieoV4G7s4U%wEW z2wo&6-l7%{wQN7LM{Zy+{~URN@nnIpJ1Yj5{PBp_gy@$S9kokB7~>0gMQ(Cd8{i)I zK@bTwaeabYYD+&Tvq14u&<#OK_J`Esr2F1l)}r6(dVFj5WWDD)ef@OpA3dOzfUy7Z zT^97BHx)QeR68zT#^07^X8IRtp=>U7sg#6??pm{nRQ{oC#c#+ouW#s|G4 zZ}B{ZM-bMXbYD$T)Ym0Huv5vAx+Hn^=4Jd+9NI4a=H)ckTab^T1zb}6lZ3s391ov< zY)jEObt~W|qPP6EFBfz|J<3vNFsPh2ZtReJNWvgm;zb%m*No6ig#8AxpxGqPP1(uJ z2sFON{*?-VE!5SyMX`D3H02i&Kba;r0w}kS4^5`iy0Z3*vQB_^4o>X%GXyO`Xv-Mh zWk)f)GEy;Cj916zSEhu1Z2$Ub@H8Myq`Y{huFZ}M3PMhV@f)52xYey;nD{p|@pc#T z(bCCZn6V`z1xHWuCNL;_%IgKt#F=^L18X1L&cUaj<}nt=K}t1dpbNK#K-zzx#>y6$ zA!nt#yJ~x^KyjK)K8K*h#^2AsFlR@h6yjgT-=u5>U1zOtFo-(8(khjqD|ZPkE4=D} z{)D`A_}p$oXX!e&@p~9Nl5^-$GinPUXjv&>wyX(M3-K%OmVyis4;T}4H!ecfpLWi5 z%>p+`0ZW**sbP&^jzG*Kqfz+UlA&7Wk0qI^y z1SHGl;sy{7FSsh?we2# z8?u@U(g{&m;OZln#U29!3r$z)J0jFwer}x7|L9dVwPK3vs1qRfJZ4jjwM&ZZ&L8KW zVG07})yfhO*vGm>|D-FhVujj&Jl@d-Z^v_;DM92Z9fWlEbwtFnc{DuU*xvB}#^xR5 z@+eZM{Q&Q2Y^v>ov6vVh_ROFpSogOzuurAq6he585Q0isfWS!+p+^~O->B}$GjsEK z1Cnw+BLavMZ1U8QwHY{j#e6Ev&H2{_Iw@GiZ(N4DnD6REc^`0j6ne!Uk`ZhW%bSC_ z8Y;!mlft$IrY$j9?J3}t9PtWi!0gn_^z0NCW%@dZ!}lQ9+=ZYr92JB5Ubew+Re8=> z#y1`JHNV}`9PY{g@C@EDGp$veL=4~qyVsdLDk>qXW?N=KeV@He4pdF3_u}emo++YO zb*N0LSCRn_U2j0EZIF_M#lf;!=2J;=z7(kKDi3P^&Gy~*VlC3E#>xmzy!p_~Pc3sO zQmF0gn{+?eaUL)yuOOv4FQxc4ya=Y4P5a~=(e9MHlLjK#tPH*>C=tVbnbezY@fD$Y zjYT&bo5n8H&%2OE4<7hlK6m*T`0*;Ypwbku-4JLScJ6G_Q455h6-)!14RR|myOVfD z0z(kbO3qe7c&kUT535n2O#K++I`04QNz2nw20f5l#mFf3^T1@`C@?|e8>}G=78@gC zwLVr?4?==C@=8%U?pHdv!W-dT{Y;X1n)>cY{x7V4N}?|E`BcT@ZnT7yMD^etFODrM znXCorPS53-_s?Yk0P1 zBuwi(6y!lynaLxFb+7+iUZ#d@jUQTEDcn{#9aw$v9J6o>c>PVku0;4E$e#}=NtK{( z2W?Se1qD>5!*dqr{hcwL-%ls0=l;Gl<=4D`f6}xgd4_mqKbP>H$%nTCqLLq*g&544 z@(MPOofyksBroVCUsOco=CidvZ<3TCqOFtPs3hvQQ#VZoVKHjIb1I#PcYO*3^pdZ4 z4-xO{loV(@MI<^?@gC$=8VjsHmbr-31fIE*oaRewxSDec@ax|B{8E4u%VDTIZ7Bi@ zj08YEzO!`_vFSB~U+`Yn%^uPxJfUQjFX8affVU+O`YZ=*Q5WN&1T0YA5%bCt->$Uo zXP+)@&Ze^|S;0w!_JMG}kByC>YzrqJL=Z0fzSF=Zo_o01?OdH}1U{Q6#&lhO`#X@8{?nz-lT^7@P&%DSvT`EJ@UI~+BObUHnq-~(#?}$}qN`OJuXP1Z~ zzV-R(>q$nkzB+IDe&PcK&(Cp@>KmBz0y-_-65o_u$GtGR2zfr;mgpu=0Ax=sxujjd zzU_{{SbqhrQ<_RTs?VF>r04--0ClsRdg3kK5dhPs6D9olV@Lf8fAl7D`E35(@MPX` zRhPl>z)%DNyZb@q#JTj z<=f0~onrb(&O}jK*u4WS6Y2+rK`BpBN0PVuf@mTJ!afK6 zV+cFa4@>QB`R zW}jACKXQy9Ke_$P2AS@KZ-QwWFFkq)@-_%tc$VQXWN4p1#-Uk*E zJq|jh&lbsru|0khCH3c^txU4FLr`?fV&A)(EC*E3yXx2Sryo9iaFsJQjLZQX0eT1; zz2whK$BEtEjyNDYs5J`^lN3GaK$QXYyaJgKs6JPK+9INxfPRb z2St`%XG2#lR@zWRDh%*Nf{=plCE$3qgWq%fIXF1rwqYQDip~ttyIhC@*`wuuWP=tF zxe!UcP@HwV;($|0m-H>H_57*0`BFRZ4b0I@E518jqT<+}EW%BHyGUMv<;6JAip_Oq z;Vdy!KS94?0m#k{lR>)^b7oTqMw>4}_FmxT`xsO9HRFj3*l8ui?kZZxP)ZyQ(&DwrThNz-+%w@qF#6&Vb^%FUAoQ^k59_(^f;OG&{7l) z{9pL8YYK&RXGqmpSl+!2GJ0*mw)0>2idJ&ZuLq4-M7QY6V9+9>W{DXX7+94zZ}y~k zO!xf{Sas_3c*-MqxhB{pKO5b!J!sa|w8wnR5K^SI!`rugaHlxxf{Fp$}GdX#nQz45pc49m3?X$VD@N0EORsWso7 zHPu)kq<}ZbqKw4H%HnRvDCh))G> z&g@sY3?aM|kp|*-$Q_#g&udbY5eJ~}co1*d7+9P0Y{PQm$(UPL zam}P8YuP8ypUpG=GB#3C=%u;W@Co+tNg!{|0haGAa~&3yDBt-`?s&1Ik+YlBn8QF^ zdAGt=F5E85c^GMI6;#EVR(`Z`Z>e1HaAe5xncIeL9Eg#H@ZO|Z;ges&h)~uc6jIvP z5WhPW(iFrOEt{&atW z+`LU&w}Pt$y8~B7vEDIvwWa@jHQ%YQkSPI2bXa7D%sZ%j>BExA_$taEHjEQYOG>WP zgdEnS$sM)Gxz5c_9oL5pk>k+0)mhaoWvza_vEwU-BT=eA^kRbAE@lFssg}iYyr9x0 zjd`;Sv__AEmF*#`ERvn|#}AnJRj#p)2D{H>3Xv?!YEFT&I{L+&d^LgeBu!q;I81cY z(qo!!YVW~jzrOZemAj=wNwev`o~%p@o92y5!`e>_ch1L3Z?wo0s^BTqhD*i=Mmj~ zsk1;|$uvlTMRM7EGT;^RqO4p^=aia+G7^dGWw`O5yXKm1>{Nrj~El0jdjvM{b1aVh!Fq&=fFQ;oLl{!A~gyPv2` zj>fa{56qvhSAMj*kc5wAm$LGoP3$;u@SAkP{w%23B+H4RIY3Rz7F}|_jF@2V&NcM2GgD0~Lx=6o2Jti1jRYr~ zI@vEU%9a(!m^r=ddvQY@-X(Zz=3~}MQQcL5&AXDNeG-hIl%jj5oi!Pp3HKR5SU-*7 zkUPmCqL4lyfBIlt`ti8>EjmS1t3V7i z|165{RHZR)ED%AokFLDiSTNiBkj}NukA?%XnpTKHGHf3a28xWI|6(}<@~-49zg*$+ zyL~2@^5RCRh*`dYnLfT?fDG<8Mc5E>5AifP&@Dfy=06;<)AW!CgVZDq)Y^3o&R=7cm6yRAk+4Iw)$A#2+n}uLtkeqU&D5$hA-h>>EO}Uqt*Y=iGfS!Ih zs-}qydO?-EteR-@&M8tFQ336TQ z^Ja+8s!%O$JaFD@W%IaG`gv#6&dSVeusqseJ>X=2o(=FYQ!uYTtyg1R{#o<^Ks{HD z1#Tu;l|%v>G0}7|P+A@A;D`8ct!`Ner%-<{=wQqDXC7L}HsilNBO^~cq-5mPAt z`ebz6{IiYEaFdyBo}P;KmvgR7rYdJ9=eTgw!c9jTjq5TKxYg2C!o!$9BZIqAgF}ar z8>3gMJnlLFSOz)Wa-net?~pm-eOlFt!A~VZN#3ANvc5vLFOa%H|E?zteGap+01~{F z>ho}P^dB3E5lxf)cKG@%DVZ(F_TptO6f77NmSR(Tt>O%qliG9FTF%Q!k35;bn4XsS z#Cda@><3%gW0zI*KF(%7Ta)Oow%DOBptx)C+=wX4?9u zXa#u9aUSrRs4HktV#7h41cT5v8(0lN*$pZWTZt}DDaIr(Jq@xi!ZlG zB46J62Q1{gs5ZM(H5((f<_Pw=^X7m1{0#$eedQ)j!%gc$5WqZ-Jpy^qnU*-C6<1_j zDki9%W`lP%cB8_)I$Ei|#SuXm3*@=KM+WdM6|y@mzhwUn|z3dOtE*p%F z4ypVJH2u1Zg99F;Z9oFL~pgfQ;=EfDp9t@G`Vm zl`iIOSAECf4d!Zab!_ZJnzt6{>s_ESxtg?0K9AAze|Erqt#-Exb%xF4?9R8bvXV~E z+wPH5kCR5sBEWWs&4JQjUm=^sh||W+jCFPas(TSZZUVons$va8&qL6#S|RZg2%mfX zhM)uaXoc)d?<>O{ZErfRBr@sg0;S>?DCKdq=1HZ+aAHKaUa`|_J&U>L%$6z7?iHis z`eJCVKeg6-{=Td5vXAkYVOsgI?8)AAS9ukfAP<>(Q=M^?xiU6(@0~9BaVh!0eY6FL z{bNDN4_#YOpujKFiE?BW6|1Gp)e4_)HoDm$HJY03TF=w2Reqems!k!l`N8PBeUzB& zZ{R%I0$7u86N#%u{|Y$4HG;^98Cg+BPiruW18>* z3P8RlytCnVGcAQa&DNlq$K9xYBMkocBrw2Z;A({elw1jJO{zMr<`iT@TZ4Sz(iizL zk*;%sgRHZhNHbS0im2CAvNGr+a4maV@8o2h+V1Z*fCQyEwsF#Qaca zyzT-fq9D*lH!joyL~stt;HxS$CJPXc5Ee8in&mW#%okwePrPR#FmUb0L~c2qMea3y z@jUvzH({)RXHDf2(eQ8MzC#uQO{>=5Z>NmzfZHRvV9D0oxY<7P_LI+T_W_ZSyn8S% z=%(39@22P?E>Tdte$t-7A>6wnp3Tu;yf# zMd&lCE~=+}V9fOY{e^}%D|t!WKLg$)SnhjC<=wy)plA*OX{WSE0Oukx{R+u|*MA@G zRUmLC5Qw!&3nZ(P4emP|!l$!M=?1B+xTDV@^Xo?$bK)VJ;%QTR|4uowR8wKD2W@@N z*Nk$v_~^)wyJvwInBeGCLTIly9M1Dv_i9%42G`*nkQ_ebNL>%!`>7_CD?~hb zRI?;X&i6kAF2gB+11P;-u0@$<#zl8{v786c3q<%bJ13O{F|DQ@7jTRF_X|1K*12V? z(}S6}gia*prdT5Fs5}4bj#^G_$63Xl|MAH%Bb`Y9Pk?l#l?JW4Xq1Y@Wp~Q#j&mU^ zsQwBgfOR>w^>g|QZYg$At6*4$3>GzVq%qBzMG6@*1Hs`y*fwf=IM#Up zDI^X^_l~!M#HUH1uSLl1h0CQ-IkfVH;yr?v&FEX=Ix;{B;NfL_v|QTM7}wD_pB2)` zS`KDH-KX|1f~KsHqeQuD(bUnLxYYNgw^Yl+R&1ef$c0vpnVv;{?9Y!Y+FlHCL8Pps zjx0fLBrQlolX}IHZYhd}tSre?n59BJP$>md9`S(AFPBz^cua@!a&b0EgQd%PYJQpV&_n*!LE>eASm z+nbMiE3?!Wevrd=j{mlgUS84p!#=8_Ud0bZURoeG1XSWy?7V)^C<}_j!)drcbKc)0h zf5;gJq`4u&FDSwC&FfbvE&EU~WIY!ouG%b4_Q5`kMM#jcBIjm6aRCzd zqbn}PFR!-~xK0W2GC(VhrB?$5e73F^Tk}c;-WFb3L7PxZ&XFlc3Y>&2LSJR5Dnx5N zdPm}t#Gi4_yVlo`x1^GMD`gzio|;yjqEzv_QlWT*%U9z;w(3&LBR3J|cx|Kl#DBr2 zU$345c0Yb14W=B=lSL7rEwer{#HW89^b4AaiT&F%?qf_Rqga~1y3X8Rpv|P>w;fKgS_!0qI4oC{fkTL!MzW?)l$Z+%kz}S&F zAYIe1fI-L?*8|?OzFFLOzsOmbj6_BmjHy?>_E&dFSK$b%%sK3RwG4{GsS+{Kbzhs; zj6DH6BMxV(9=-|V0o{iwz=W%_9O6N%9s9U|(i`spJps?Uwn0wOAj`z2eLUz*ZtN$x zHy*EgoEPI`BGbFzOxlsZ(*PCDZ?E-Ho|o<+pDO)voPbh+m^Y|cw8BJVs2no==kRJ#`BwXhR(OL=IOv{@Q@0E7~YmV%y@-rWwr@iBA6EvhtCrxLF<6)uiyYcdMHhV7(qB6 z{5zaFe>P@soHMHsrIdJ*)5ly+d|Q4Ne`nox0RCj?ffQoZ^FTPK4@8}lUJzefKVzm^ z3gQqeWW?4HAU?N*ss{G}e`GJE{Q>2BekxV80k}^mO}VljNo@igCV1O8q>A+|V}cr( zMMC)}t4-x-&&*T&ui)aHT~$mn&HJTLsKTy{=oIBVUIfym{OMZtn7b%`Ot+wU8|bii z%F-A!JKna7Wh9w_+PKXwr0l~hfhvRKj#}U}&@&Q?ZIGyC$)N!b`=_JvhWPvgMQ`$m zc$v5jt4TRI1>jzxK)Q1m6(eI|8aY2)NU#|*QAGU*22;U#*2ZCSP*3CC*0Py;OPfyW z%Frs9Q@pdeZ3*$qm%M+CuY)E!oWy;{d&t61Q&!0KS@(M9`gPkgAF4-@*X~*aUC>AW zTEb(|>(R!ag;hc4kkuO8JuahyxBktFCkwGB@Av=2hKxsRDx$1_U!@9%?33v27*}SK zt=t=TKd<|iU%qdq$M8|;WlGfW)&&e;t`Tyk}69<1u*s0}b!yuJi)a zutE(>=)#Fk#w6C!{ct3Z5s^jU=Jxr2MJT2X3W*U{&`35L)_ZTz5hp<58SR`p1z!FC zc>37B}gv;X+l7HQ9wEhiV6w}1VUHoU8>X|C{hKb zNdU3Xt5StfzB4}W@8b{Ga;=%k%$+;;p0oEp`)G5j{tke!q?=Fi_akDXv~PR%W30Jv z=jR||m!NM6X@l$>i!K#EVE1_TbFM1sr)}3rVDh{#p+JyofqQ-o67WI}=3B8pz0pOZ zNDvR9R#{9hwplhWt|Gl^qf+%q%I=R-BuGF*ZT=INV4o(RiSX3CS#0>aCoqidmW>nw z)1W(}Udv%2i57e>M$gmbbsWEa^)tN}7BcXEKi5bCpyLN=j1 zu>%q3A@r>}WfkbD{{*y${lNkn%V*8k-uaj>USN>Gk*8?qq0~3|EURsNUe(dp90t_N z0C3y&d80cJaIZUtM=Vx%5O$&=vnJa?@9J)A{rqzxn8Yvkl2Q~LkdRMjI|gi7Y-xQ6 zyDGqb8L=jUHWMYJJ~Sd)>HT5rCEr|M)vKd<&u_E18a105zrSANrSfIuFt7A-i<3Qrhf z>-3vpq3A1D=85EQ>|F@QdNQ+MG2t<`a9Vu1L&){M2~?-!i7gKvVPuC}xmR8$IC~jaakyBKP6+E=KDa~`}v+;=%$ddwId@n@M3~jarTM(JB`~~d= znRq|g)FYX_dvA9j)O>k}0h-lYR$OhBkH-`}shjD;OhN$fL?#S{)uv5sztEeso=3mu>D2=`dp>(i>j*pSMX%0 zCb5a67uYDPIv;I_=46eszukN)jZ!YC{TiG%XsmXRjw`Sf7haX$!OrvR^AzpmOM>&W z$&o?3JIOE3?Ug~2_gv+p-$DF2C3jZgXZlpxN6V{Bog_RGtbd%V7#3NK-v1K~*j&bV zwtHLWlOq;|*N+Na0jOQb07jV(wj7l391QErqNdBd;Ug)uQQy1%Kh zm0dT8zwr-w&muCHEasCZQx*!Y@w))e*DOw36vGd;-qw=(E-NDgN!v}0bh79P*K1PQWEJz+C&WzcNMcp+o^wgV zAp3ir;FZ{H;XIM@hl6~sjUo3ik$PnDPaNe3RTzHG=A^p`H(`p3Vy+Gj<60iLE?+IX zH1d2iuRA?GBT5PjT!woMYl&!l!QJ<7fQ+q9hlgWNfsdggbLlL5(3^+dnRe#B>D4E0 zI?D4iUrh~HaQb2|I=SqAOaIadJh_m5ZD+MdTIhifcFZeAtW5=^`ATJoOvS*jIypMg~n zDBfj?DaG2Pqau6(9qQ_aWrZ*H!R*=VA~%qD21;I~LMBUg;_X=(6fw{ZJ)qx~yz|4btDjd5)k%?mVLtah^3X#04Y>kL1oSWSr3 z=A81uv+$^Fu;|Mt-&8Us&CCbV5Xc^LRuS$WP#^Ot4n!c{(exd@j3)N1_{&3fFBi_l zX7becPSG0kwqd^C84udj4z_CMC^tOS?p%vxa>y659)tZ2=|K)Lf;NoIkY=}K$I-Q- z!7?lMf~a=i?y;Y+6+?D~t&;@H2N$wSpRwI|%wY+1Y*#I5toz*NKL~Knu(lg{i1$kH z9z@!TxU}JFF7Au5A;~{YpN2&CPIg$x8c%+bM5l7{bV2^L>b3LdH)Ew$2Mvp~>#%h& zym#ww9o(~!N=mF|;&@RbZVk+Su0*k}^RX$8HP51J%H^XGXRXF%*a}?%I4CU*#ftaj z^KIcG6W=`%Dgm)ECwP!0$BrjQxPOfZM3|#fwNZ7~e_PXI&ZfA!e;PS4WIoaO zV!^$9F@Cq%jq;$a@ocoAww6{-eXClLs4N3VcJqgG_a3R?(k2zU%AgkDhj%CD!|Mer zB&Kv1ZBb*|mU+W2U%ten3v5ZJVYQX!q*IY`Xl_AA?$g6pVYfn^|9xLH&}OO>%lq&o zu7PKz?xez_H|c&A5=nc0kvnXi|N4?kZ$Hx*o$PqOrpl;r5#9ItdfrauOi;^9!DRM9 zn@v(AJ=cjX3k&&RHR`M6QI2a>jPT(@W^|V5fBhPT=gen9QTPn7Y;t}uS?j4#S6&!8 z64iJ=^|d;5VcsS_YW3^rnfKC)Qqz>N{@R%k)=3d_j+7$b`XVT(Y9u($PB`R6Fx&*bJRS8+N=@YW5%7Y5eR`GlA!s`!7Qi=6D-!2yyO?JM`s*Q@A zpX^xCF&9$&^{a(Z`l?=jXA}`EkWe65rev7{4TSm}JPS^6vD4^JP}m!A)Z-$o{!LFc zBMIUi+_vTeQkc8)8-on!1BvezpNhNBTRh*r2Rmv(Q+!jmsjBrb>99S}^-4V4v8C7- zyG**72Mm5AED>^JP5)Q`(?`XUFJ5&M_m11P`5Y7;*+ZI68>JxBuSOR4Zb_u!6z|z} z4<3YT;T}*wf;-dcTTB3vs(MT@m_JE31p)@dzOx##*OM%+sXK!}K{Oy|V*7A$b|bzd zFl(J89s3=mANw=B661G%9$ie3!^&q*b}HI*jkF;%bZGYmwY62s3wfFHz5d!K^Q zU$o7{PC(cycw zA-+Tjg2&~pfZ5{0$UOgdUOY2mttaL3^82oYip{UB_9%@TW4@!(0`YCkM zb#He@dr?;jzuJ~}zbSG<@(RG7nC&0&5+z5GF|tG=vcw0UtBvZf$JeN zbR0)N=cbo3L|;b`V~{NLDuMY&08op^GQ+muWuWW{)*k>7kj?$pVjHbcwN`aP^=Oyf z%Mc;q`Ul+eFN{UTVm80Sd*_;vG87~TpW`>M3sIUteFgxY9JpZ1<%|q`FATia#y<#v zG~u^#6iFMzl#uCmHxB*Lkbbs&#rJmBXJt#Bd+MsWfx3jECbbZ}!U9D~@i^=)pTAvx zJn62f>+wkw<@j)*7%+Eam;C=1=E#Pf9dWd3+CfxubjHocYP3kvCm5M%+x~SOYDVD* zxNJwQ&qI$!4P9q`ludrCu$_Cl!01NW-LPC{d&w?Oun^|6NmVnc#Kj?^j&3C7gdJxr z2ZlFcfeY%9e2}j#!uT>@5YYo0`ik*vx=;@`itoRB!WmU0xICz zdLt@!%e8@oZx)Zuut;qZK1*z@jMmgWTT)Z-g6ML!=hAA|89_^I@(0D2Ph8zTQD&vc z;%Ebo?lr~g&Jl~j)cQf4pgDuoqm~V@#x`71T$mnqNKdW6|L}P5B^#CzH{=u039?9s zim#9lQt-zX`p=)4t4d^gHO&PQ;OE`-L3^v|?tl4pda~IA3&NuTHS8YpDwBoA0rT)( z@x)=y?gO1#;7}fp`Z2mHgBm=Ixdu8 zs~-9BzbH}bobSP;eHhmxqs6U;rG*00&DVm)lh=0&tahDlL8RN`I$}WxAJf-Z%q4Kx?CcIF2;WFG%P#C0-k+9%3FnGLbO;Xp7z5@(egEgy89NPXMSZYK0nKPsYi~0~ z$k_kTP*C{TZ%-Mbi+sr5j$K(1_LBYcUuVuHVePU`X!Ej+17b2Yt+ckh1SG&)Ya?{s+^%deMXCED{{8P@t>mu(4`nA=rNo0du3ZAom_nPl<# zz~OrlFq1Ee!QHh&<%M&$Cb(c%$VgXfGAXR$5`a5Xe6i!+0h@+JAXoOmG?a*)qFRJC zF;ttjW$w~ zktC!JI7+XxY}c+)h5jW!|0)zJz_td3GOag!^f#ba>iP3h^B*|1L>N|E!KY4%TK4N7_+m5#$`jsO|K#hwy#eL% zD1$k#2*4zfKYOs>anli zgISMDNSd*r+dG;K{bh*-QR~-8vhkcLPwg_2AtQea)1x)Fx_|%V!ZzSt^qhUIMUjog z+XxTK*=cJ}IR0A#yaT?R`^Ta32h#Y+3Lnwfubx{g_!9+p74RfIsAF0rR~|HFhe7?5 zwf$}l$N~Q_U^_ryX$IaXZZ6`|-}8=9E%1D=-JY^ATC(|#+{IQ#J~n7Nf+-I<5F945 z?|^&UoST?5LEH3x!`f>McD#-Td=-3FFvO8<&gk#QfYeB zg(==qWFk+U2n_-m${0|N@wL*{4A+`|Y4g2FP5kc}5QihTW(p{O|L-_Z)RW>euok#; z0C|50N;ngn&uoBP)LN8P=Mqw-G+nbLVuVL+7(^`Gt_hul(iE$FgA~}S8Lo*@_o0_Y zcWp-KkTW*Qz|?8%=pA6-k2VFDjjJ2wH+R>SUT?N0~^Vr|pFU&5`r|RT`LkCodDc|N854GgL zHv@lS1yOv2$H$OaBRqDOI0@4LpAis_XKARd4bPybx6zoP^g!gVCX}{lr zr2U|^aejxgG_Vv-8lLt)>SHFt|Nq^-hbd9arh`>){Z|^%LMi(qhk5IQu?78$c^$=o4^E7>S4bO0U0|G?Y$Pwn5ZP=Y1TCytNhRq|G>-R13wt2V?)Ij&cH7K zBKlXHG{!5`audphYe7>-L!W?z{V<$DUVRo2{IY5tkE3GHG(v1pS+6+P9_Ry+C7bmd z*huSkFF~nF?B6+*oeDmys?4KqRR{hatOy4exV^1F$cPrB^tcDK|9xA+whzG>VtDV8 z^zdmdS_W0PE^??NRNPx1IP>-JabRuk~ezp^p?OhW-q zC^p)&f78#up}ps`L2}h?*P!IDX|=zS;HhUKDlFwZf4tk6{;%qUK@DyY^0X^gkMM3! zA(1xdnrhlt5n6T&sIj22{hog(0j|Ih*#m#SKb2e(IC+6?o5-lJrn?HjiTi%xkX1;7 zY$3A#BCB`hu3xaR!ZW!@gfFWBoka7Wn{-`~e1zU$^o}r?CQ>$r<~L^$CVmNuD;D2mkfy&)zeYh%Q|{4b$NBHw0p^Q%}4HT7!1=nj8_lM`%gT(nuJhDvJV>u zJ6*1B5r-9h{k69!jr+UHT#g~IB{X?V+ShISV{9L~)uR}|8+eA%HIaowIB_N!Kn7Q( zaYrUe=7h-`OQ45K6HwRP@A5DEQ_B3IZkN)sQy=!gbS@+T-A8*6gHC(Ymzv)(;jYxR z{AJl4st5__)vz{F&%ax4cOzC#uiRqhBrgUjE(FNTlM3h#ZyXU@&`<=(9jh7di*B&fl8WkK* zU$-ZA+T^U4RrKUlf7h415P#p^uK`!wcQ%SChWaJp_W{* zsbM16JjDDXGyiV30xPm4^~f~hAEpTz^^vEv&i0PKB_coHaFpH}(c*~+XV^oKjPCty zt9@rLleUVT6hCazVElQ^Z*yLI(y#lXT@@*FPITz7osY{qA0v2+nMK+#JLI9pDhu?v z0SzYZFRst!nYUNXmehhx2A6{j9M=WW~5Yu}RI+7Dp;eTR!#XVeMtj zy(N7W9;!#F!Q*bi6)Thu%I)4k1-hW40mH&?ShjH9bvZjb6=brzd7+inut~h;6-wok z0&}g&&FM|;3-jiVM-yE!q+2EU#C_kGAmyU3n@bx>9RZ2ou5Fn|ugJp|%<`b?L<;zr z)=|F$?OqauJ{r~szlni-bMMoGM|woqVEQIx=~Z~IMeDD>>cQa5cvWtBZCCGajJqkk zwJ^D;q5cYM8uB~RYP4EG(_;4Q{@#lSPO^8iyj4lq#Vp~zmjkg~d7Q6f^=)oc`&?Mx zt}%~)`XX8p+o168yla~@cZaI$uT zFxLHQB>9TwG25J>m@2hZ!ZGy*;iaK_B@CCQ#s}_p;K-W;%Wo~?n^&rbUc?&~JF22X z7T4r+;*Xgz1UgI{*gx9aC;2G9ZaFo}Yd^Afi_P$S0%WDyq-`&IJ=R8&&d|P{3 zt%xC{zbN%V;aAR0XX%qXf8syf9x3;4AVcm_w3Bj!^=v^{+jy+9-ZNvlfV?pF;iqM( zaUDYPl^V{f9EUaH3EPr!f)`ee-6QJ`^gRZfe8GZ=^?2_a1ZDkaA=Op~l9?vjUloa6 zZ&2vg9}`?8b~tO!L#YC)^#U__{YfRU-}pY~--k+vhkEk!L$sAI^@8&IV|b?0iqCED z9L;K?w1cj;T}$sV=P*-`%*`DkE}YUtO2IZ(aeVPeqP;jx(T=IMA(aB6pN{@B+iUDt zVh}uNLzY?B+x7jZ-+q2xPq1*Izc|lwsk{oEZ!Rl*-p*SWEq$C)G@X}i6o*pZ-4uEG zyzTap?Cqax`{@awb9Gl57e(oBsNop3j^#1TES^#qoOiDp8xzzV7&jjrYY=Q?S8Grh zpu~;E8RPvH27VkyNK`G|?Bczcd94#?LSG>viH7NePe{%dTzr68)wtT7)mA<`C2sEt z>9=&1lDou1@v-eJLv>>Dm(n3_HxBB=%4W^r4J6=if8}OU&c8lC>bRSrS{<{IGpZBcL`~A=>bQ zZktaXnnN!lqGO15+)z(JI5i^*!4A0TALKX(xRH2l|kWV!M{qqv$5soZA5c@dNnAdo76(WIaf?4d@r?I^L ztM2;)q-~Sf#}}Tp|J47EuO5=@oLF~L0-MV8y?tWnN3WiF&G!#x#k$XB&b4LKa0mV9ktvbSm8e2EPq$bM@ppi(ZWI8?I1ZPHYa`YKM;*L-uu zeHj9_dBHam%K{^T!Pf@IX^%S`l~bdZ9^PE$3;B8bm_Ugnr5J!iFN)-(d-p5DKON2; z&TLwBNZhR`q(Zzk{ZaBmbco0Poxf%yvC&`%mJtQm%(4?uVrP_<^0YaP_w3n zAIJQY)ZUE0*3_dfdwx82Lo7hs>7(4;ly>s2+40HEtix}Gl#p^j(vq{cd2H>vmY$%L z-EhL=qmh<-TMe#8$MRG~(4;t0QN{iC>2>Zv-hSXnnkC<}sGcRR_0&GIzC4!XASV-h z?A^8bL6*g;eRckqJEYXXET+V^jfAU%4Sv;of-m0TtXk?f58&m;T}e2)DWAi06P5iX z?j`9)8~1Tb!&UR&Ms9oXlAH;oTZ6m(Zu82)CL33?DsfwlJ6+q7_~eliYG7TV&N~!) z4}-`2Z;8!cFaf_y=*O{r6*7w6dym9B>>Tn<8UhhKlW?{qtK!^nj~*We**z|T zm~$s#YJ4X-M50=gT7EZWjGWXzSE{ZkBF9o@g-`ty4{ww8#sce z<;RDA2QZQO?>-X^{C?yXV`taOX{OKoCEr1|p6FOND!gEF`3yct!-Hpj(rw_yhLeF& zNJD*N;C^s?DbuC)^CYFaB|<}TaR!64fjW8F70EkGIKj@$AD`Vc)zU99J?wBM25V>i zz&1N|=B^<@vsdQ2KD_)m%I7=Xqpv0?eilg zH&oeHDASdrIRl-ULXrMAYQw2C<@2$*)0Lo}pW-l+d7NrizrY=0OYZG5a7;E14z3#h zEX8LNUJ;?`@lvbP`JasfDgDc)&lX*(Cu6gKTCnY92Ov>yw&oqnw zzb76O6%$TG%D=!ouT0EHq9fdiEZ(iJ@=9lKbAN9;_^ zY(pH2m(tF9+nnW4u}LtR-hh zLWZlC`#D{52Gz^ks{G@I5`sMLd3w+1;luISRt9@}LOF%z07bJ{RPw^M{krXsHK202g=qZf)cD`V}Hv4|7IBb@2z&!$`NCkE-j8$ zE$PV7T*kH74h+57wJ5L&V=#@vjXf2J=YQ}rC6SkYb9sN47SQ zud}3e9Y7HDkhF|0GMPMazUdN(bXp*#$^d5%eWy}PxZi_E#@;W`X2i+z57v5oHTH{&)EXL4zqaD&ZCzZ%<5@X+851fc+Jvd{C7 z^vx2i+4OjPzHn!$!I9Y1%Pig(vK#gmV z2Y*FW;2a^AjX4#!^7ma$Cf60ALUyQV+FRy)h&%c)zD9AiOG1K~4s|%Uk(A({@n7IQ;1*)(9^m~YcY)$yv{x+jlZha3w|ZrLr>ns-K=2d1 z3aP5i1gl^_N~OK59mf~vkn|zL5`%T;j-h#vPr=`jn7fwGHXMP?I4>Xt5*hSjzTkH- z+T_edvkM?!MiJt@q9bsTr4EbYUHD`i`Eo2%P=!~};q`d9LlJ{j9CIJ`8-XOsoJ)L{ zHk9cw<}-QbZrILV`pyL3$O$&4#{#kYTOnY2B&N#yay4M3R!j29&c_Ne(d|i@d2Y7) zi3D6ifM5fA1zFuP0PS+TZZ~R2!CNgw+?bG8Ade?z-NzEN2sqqTU;h%{!@Ls|=0QQ> z)Us=Mjt+mL;IG$|3JTpR8vGaDKHZ(zj8}eN=wl|jx?Psuwx3)a7_X~W#E@72=SQT3 z`?y~bPpKhigzE{zs>wu2erDWqCyZk4kTd_?3`c-n_I0(YX_e-_UEkejSdkt)TliTp zpSGdQN@T=iqsT94%WRm?>8}tJ)m~dSw4$M&<@QfiLZS1d!{l-|NtkqD-X+*~a35!B z;3{_~ZXD5npnAE$5|||N*9rvl=xCQwEe`fBq#xM98PK#IyujD$jc2uC{iCd4L*n1E zSpRndrhca#TM3OPy7OONZ;3}{f;AJHyoO?o-jjF34$>lJWiC1^eEhmP*3*7p6R;bE z_OS+n39ly?==g*9-ZIN2R!$nDaqNbB7R3G@+I6?sAKv)KD-E`>_`ka5%fdX?arkO; zh@6tg1bgj>_Mb_#Ix7;J6hE|47t=!S1^z`I(rU&?bKyMR(xED{fqaBx&DVh#-}~;i zRz;ZUJLlXBM;5adl$^X-W-|ty%l3sR>9q}d!_kCAndRN!_>L9#%>t+Lp=~+g`O6K8 zNWdFAq#N9N`{7N#EqMa&8DJivTgeO^nZl2^z}p%6mzD2c2g%-ppGWBpVacVs;F_2b zJvYYy)Xz@uehD9g@AkfYP>6z`Y$u5r%u#NvCxb0WNZr%k8bd#doA$10aruc89SKmJt(8MFIp-c_bH3rrUb)w+_mP;uo~dRBZi=v%QZo9PMEE)4UM8BE8rW^ zIPGshXUD{ytE$yUDiE1M?0+DFAU~P!LKwZ$mfpbC}bMruwXxScT=>e0q9NdID%EB?k6p1tUg$ z&wpWu)vQ^(a@5V99UpGg!V_PppUEiRld#acYs2qa^O$}Lajl8q>>qY*Xden$d&T7% z0=eJ*4)?z_S3gzm8d`SVKXPgTfmN=YlkOXk?#Hi?X{CA}bX|=*Y`h^U#tu=AF|XF= zf{OsS$6JI`wf}olz%MZ$6Os3e$`&~0EZ2`hi=ME`plY4u_gw$0~UwOuC(_y z&RDP=9N}D(iUn^W>?R8*oa2DU<0&;z+{oU?mjf;K9FZRg{oaviE!Yt^$8ZnRbS^U= z3q>tcY&MB7MseJo^Zyy9i_wGU;;EJ@cjOF~;{SZUpvse`f@d{qJ@B3Ua(y4Fg&US` zq-|3dkAjU%N(w@_G|4O+kJDf~Zo8*E^8cxrvU}eFVblfviH# z6~JZ-jF)wajOUM9=9}?+)+}WfLn<;Fe<>v}mWgXO4SDuj`RCoHb5QAzR5`nhW z8vKpO%m-gy^Wvj*82NiE-=Xaof#>Xh{FmD}%}-$6tpDc-cJRSj-`M_XN7V2E7; z%J>8v%`3fVuXxU@rMTu0O9b0#mWQ=vU5F#rn$=5vHgF6mynicGYLJ(Vr&t zsGH2>s8C||_b`Z&r$H;qs|XRJMwikzn3%+i|Dtb+L1$wg#V?wH%$;mPExvlVH4cT% ztI}^4Tc387o$C>KL0owq9Cz%KO0Y4(fiug?632k+nBevQ1_(MKeYTn6qyDI4%lg-e3 z9M76v}o>eJ!9%lz`S0=8o&S>bGf{0^>YdZCb?9?s0lwq^uQQEk{a-rpwApA-OL(hb7?j;(Oayq&LWua#k@B!&%78MImY<%#= zkrVtGh&|++g3~AlzUg*HS5s;X9r{E6+r(NY`trOm5w$!;3FlCIxl0CLqnXk`GSLXV z5o+zA;+5jJadqm9bv?YBN&rx6%9&@X{p*akkD(Z{X>FOF6SFvU;|QvL+~F@CYsh!3 zYjR`=+)Z3*9FV5QUKo$H_C!nM$-#{cAvgAXSFdK{V`XK>hL{_So0&r1(vs#3X_nat z(R^5;KTOETX{{$RJr#J%Vq{Cm36JZL)yY#d5Tsr_nbtnE8a!m=up(hNNrTF`;N|Ky z)rBxtRLo|5Y|KTe;%a?SLCWkC!0PA4fV4V>zs_CS@^rO-{kA?m&tq9QUq zZI>L2z&5E2N!kII)lCnuMOO2yQ)lfx@J~`zF#)O$Av+A=bUd*-mq|~4GZwcqG$h> z*LCU(R-D|>P(IYzYrfuIRc8yPn9Kt%eM{j7L+$H$dZ7EF!{N3#>+sc|HiAXvFKM6L zVlbk6>g?^2UC1WrMUDE~pP}aLM{UohGW@t<+`KJlWc)A09=s3vZU{6a+)_og3ej|B zz5ncX_Tfv0HW@z7gxWdmSaDwCwS0VP$N7Dcc}-rSaOfnEn2-`{kEyMYLBr4;@e;ni zq0M^rwH5VYxMD$?jF>oTm#|pV7*nN9BX*^{3da;5&uoM62_cCRrlab+4W_@u0gTyVsdB_jP*rW)M=WPLk1S9*A5@BrUwI< z(6iAE`E79>T(XLC(~}w5=92grkJ^`3Sd--TRG*LSpd*ORpzp;`npM96IEY4DV}K@sLhftiMl4uHF@dfcgM*uRotWo zsX^ZL#0)IQUe8wc>eY-3Wzm8iPt;M8FhY50*F8MKf(v5D$eJiI!}4%DO*Zxqs$_z5 z?xnfM7)S?~h1Yv|sbn`b~NKbub)7%+l?E}xqp9etSj<3uUgC;L12-vc4kOi4=N z;K*%W8g-_V>H8nDm4a30+a<>WfN>GPULW$CcGtu<$Qd5}tprKxDc zM(Qt5;7v+E=d+m{Z0*Fm%2EZIUX#lh`cJnbvs1~B{+KK)(uGz)EC-w}NJCqcBj(Id~=;ZN{?)H-n zttJ(8XYU2?6OU8ka`*&$b&!(X9*Nh~sG#S^d=Ea!QKflys~>u`VL3mVurj@1MTP!u zJ4ywgJ4F{iLyNyYjk1WH@7q3TF_hUP1Vb9{?_~j;LJ3xEF!n-~18V!+hthD`>na`nKqd$B+MtlAkHE+YcSxXf@t(o*y zQOmctOU`^e-1_xy{XGqzj0NR#8lN+VXjSH`@9PA`QayEueoi=brBXkR8hx{1U_&zfBK~w^=OTS0C$U2-Gz$=_w2X)6fcN? z4Nb*`fM2jYI&;C)#^Y|;H{jm1(OwEKr60>>99qp-Ee%r~5Kh#8J}zvFGK&w}9~&Dp zSH%p6dj4Wm5_|#GF7~IB+TTVBLXD4W$ByvXX|v{!8%v>tNiPFjfPJdKDBXV-Yr!z^ zWLWi`#G-nWMzgW%Q`rQq;X8U9G)u;0k@#M0QQQX~RaR662eEn}p4IE^;-5cXYUys8 zAp*MNgNaS5Fejd{n;qw!?G+VHMBOwJY_kh{^Yx}c87|Pr$NuMmr99fs6=IFRMT6jt zr7`(imrD+#F^JzYvEgx(p?_D~2nm zn`&u#0K#l1e#8M~}GteFus)!u16EU`O0ncN9y%Nz%D{4+H zxDw>wUg{uK3j$40=%Ae~kFJGEY&C5G-D-?7hRZ!kD&Qg~s1 zDhMa!42<^qxARbCc(b<54SI@Lp`v+&R8P&d98qDNNq~W>;HKLczMjXpcs{vh_@;&$ ztMzNmGxp>pf#lS@rMUDgrYBFXL2Ckar14Ab534~%$7pnKSRqFw?e6tj)PMJe_N80s z!T=ZG!yn3zI(e_dCG#EfNJ0eGtEL_D&*`b z{13|rJeYebLJtAuBW?w}23%ih3Z9LG5Kj?$@XvZ29HX;4=pnANYc%Sq@lrW6U}tED z@`u8T(wohgi%1J|2!5b_7-~=;Vp%}4IElU@t3sjkY&Q^}Mno!<={?$K%(^08Zvq1Y zT}n7+G%bi1ez(#r+6Z<=G6qdt=5C`s%Rz7?x4$!bb+x!vfu zIu*v&11+{i^xjAwLj4kxwm97uI5#uXR)2U#Q*dZ#U|!-W)g|!D?=Ik5VZdbQn2Ax9 zG7PhPd=U^7R;C{Rm=+&eHEN8{Uds|CF`$~pFbPQ?^U6qRk1-;d)CwRxH$7>?nq*RK z1Ij{V7@G3&3`DlL^;Qo%iOGqHclAuCeaz#DfoTRjg6Zxy(x}jpF++>K`&1dwFNCV| zoRDf?8QM%|hDYs%(iXdpdnve=XFbPC>1Vqpl*y(XJyFg7g0b3&*zRzNPMHi!#lV}V zWuAuHm2pzfbXV-^izrhGJuao!2Jz#B?oY^2xk4MU=3=rLwAMJLr^91o4I2yS0lN&V zt)0do%0^g^PqPFa9o?zBr|AHvl`e@WV@#;h8oaC=oxPY)wjq&56$viyeZWL!{P$*z zzgJah4gag5du1IxW5VF4`K?HVWWf-=VyOj1m&tg=xOLrz^fbvB%|PWIC;}h=C6hf6 zvGDn2Z#vd)UahuA9M$PzKEjjLYEz!cV=xs<#!#Vl_ycAF0s^=yRCLr>Po&+o#Jdz+ zz&jQk6#~l#?OSaOL7gwup7oTr96_BFHsbNpwd`EwWor>+Zk}qNrgSGupQ5U%W%p%> zV0x*ceoaypO#AJG<0SQj={oX7Q3{>-+;CLn^^~?F)}O-hH;p06VE0iDfv0L0LPf@f z{dP&gPDjad>(rgBeqvodiQ<1ttQGQgRQeWr2=(Q*top}Gxs5V0lDhX=Hok>^btKUi z!XS6&BNpyIn`pM5w-*{ap@dYY9?2dn>5Ph?@%kcCU5W2#b!tVwl|@-I$A@|}gcR5E zmI`=f>(Wtms-Z*iccheG&8hk@UY^m}W&L=0Y?>uhO`SA1`}Ph)OZ3yHw0P?y@l{v{ z2Zy2Hw8IhblDNX>P$+-GZdG9fK?_M=gYFYY@VABqMCTsv+l#v?5w0^Ewg$iR2!+Wae{A9 zVD^(E8*hfFH=ekX6s*8~>)?^=2AJwrz+Ff}QQiN@XxdO&rj@RCk8zV&=EuNw=MLCwu) zRW5x#uCmr0cWyxOqrbl38^`RgSCZXB{)G-W4MS#|71m(yBvFcI|n9%uI_$LX*3Lie}Wo{}au z)aJ9J&;4V$%k*CEh?L%xK49+^CV#?yu0{yeR5mu?qfu#Z9h;S33c*}$w|#kw!&U;- zY5jyI0{8t!yJuE^cBnnLqz~Zr{fDlgONEA`y3ZPN08BxwrI&#P((bFLIjr@$r(kRu za~pGGJQ=Mmo@@Sb3%z3JDW?6IW;d6w#;pnUOS~@w+wml){|{wznJyIHaVJ)Lc=To= z&A1;|>sgdFtC!wIsB2^#l{@t1q;#bf(=pmXd`cJxuh113kuaQ~(muiD1z3&U=kL-m^Z$G15>q9_BnXGX6U z&(l%ut!^Cjbf`C43=bF?7!(h6WKQZ2D#QGg${P{I?{p>|$)Nh_aeI4vcrv+#YOoNN zhA`%t2+%{1szDm4;TE7nBLRUES0RoHl?{#7bhFc>Cp`Wvj#LW@pv?JP-R6y}(MJ!{ zQwt|{+n*XsNV90%`iXKj7Cqw?`utRHL~3ek!E#Se4;_lXOaP#+HT%>z5CZYNJ!VF0uw5<%OEe79!#VvLiI_tZm%TFIEe z3hy=)q8M2G>^{%09#Nh+s7EIBm*Eyu8!mTXs~bg_qyLw{O;eK)qcOOz=}3RLJk88WC~`@|ItUifsKiLpE3u#h z)8Zl(AlZ~;7wc)3%`_91!G@|#&KJq-Um6}BM)kfb)QOIcE^+;3&v2vi&Lp4f z%ZO_1S6*g{BYtJ-xg-;TM>YAp$xFgcoRLvsjb{#ZHL14hNW7_Z=w^SxiZ%yfYZv9L zAAz4+8B`vb>v7{HNgY5{nKL|@cTkmj;u?iYeo1RTrC%H+R@ZWq@3Rq70*k`8GT8=@yO#bd`yrs;HlyuhVPFSz98|6!Bwo3HhA~v+vdG1|L!6;Hckz@amJ2a_nf#cmJ z?uj*>#!~92gK`V#Qqt2IQ7$=-`1@WhOxDc!KHt*^Q;Y|xQQJQ0(DJoXHq@2y8uQa0 zTFq6sD5jJ0$CZ+IYK^gl_rWP9Nx;xxGJ?L=CnqnuB z*{E>bQRfE{bOR|PWKorlh3Mqeg_BhaGQvknO>}=;e};PxvmZ^+D9ltAci%?CgQMR3Z@#`R%AtVVf2?{?)zR0O6Xuoa|3;H6%#y zHb1c;{^n9_5VoL+G~{{{u_myla)dW@G43(OJ3f5$&BYL!KYFe2<*;dz?iNp=aeMRv z9?H+By)3kODQ4()ZeZ9U@9?^tzi+j_Z6JKAv3r$J3 zm~^{O+lPego8ear@%req5%u|JQKLGo{SajcwlqIb7G260y!CtDclU)K5^s%7>x6i^GU324Wi%Hd z=*0pZ-s(^W9an)MGgpBSv(*z($wA!X+ROLvA6|PqebPE0v&+q*>=Iko^z<|hT-XP@ z-n|}`xBi_2Rm$Sm$cs1z1gS^_n4sJ3gkL4mH??pt0QbYHYLMD#dU)-Jtr7mmD;r|x z9A=!p)EZaOf8-RS=}jxeaS$R>U069;E2izZlFm9tsr6ULB#wVf+C7(Wl zSa~{i2ZS97Ga&!M^uDa}&3IICNKQr2?3q0llgQQqu6XO7Xcsp%i`(-u0`5-R9Ss0XvMd8XY><`FK%&T&w zk~WqzGEyWG{pu~}o(Zzm@>7_rWfwUXjv!>sfrNxClFVo_^^CTYi6+-t|G3+`Tg`?j zI1d7K?M-M9yE+kuxe%@Q4XMO-u#xqIQiZvC0*b7~8r_Ja((DLN^b*@WPr&pYPB`{u zKdedm+ET%Ec~N;IS@YW*o4UNA(6vrzH?3M}MZ3VGz>CzG^XAYhzh=!AO3A)><)#1| zc2xF}AEw9G0Mpd|kZjNAB|!YPPJwZt?mI6xwXXH!KkZnV(gGYxhygK5x|^lOy!s<- zc9+WkjQB?3@Z25&)3<#$sl^oRoLjF-P~Skezth^jkB^S@!s*)Deq}(0_UR^C21mK! zrM_U-7$uWUauD&o;iaN`@Uvj^VDFIM+nXZXkWc z7E3M*1*XlD#EF4PqR?0#YbhD;tb_;wF9@qM{diTs8&mvCqgkF}ZBk$E$Nt{5ousg* zLB9P*z>2PY@L0k2n@oULiCdu*a#ZcXM5B+xVnjk_#}IFh ztIO*<_G^|NcY2vX-sNKvgiO|4I-b7vQqOi0?y!lIh^HeI7VIDF5~KiQ1|CP$JOI8f z&+Zf?jC9`dZ}NeY?4M$6WjIMPQ+D;u+?yFMUaO=j0mX0JcVUZAD{3!ta zcYp0%Q_=eOCq!K@as>~*IB4k@M{W!6o*^_|iPrEVniO;szh|Rn&r=vX?yd#r!x53 zjRw}ieNzPw71eZqqniU}fuZ$zI{dc_$k92)%NWI(o|R1|v}B5>cm zKO51!ZcgnPdWq4LcP~hN8=qYjKeAO9M=v`9nO$5rO0)@H7WNcuuME?(Gg2?2fv*VL zvNk-oMW>-T86h6TOW^{lQ;wWLMYY|NF+a?hOYQ^)pc?u==|7@{4Hv^gb2?h0vtBW5 z_V6Bty3YJ)Lm>uLI3(&AFTey~3_Ct)+NG>W1w`+8tsT1<2}LNMCubhoR#`ZL3*VoE zID4mz^Ts|y#;-VNjD$u2JRj7e6SdSRGei}{eDRUdrFAuYO5S2_siWO=QWhnA=V*4N z`1qI@AW6v30i}*x&<$0xk&gVzj~?eJ?r{MW%Ll6APMVFo!7jWV2*b3>+Ttrwe3?%z zzK{MXE-A?z1aI%g?%(psoC}a=jGH#anQjMu9OV57w_;=I^THq?EQI5w1i%A+4ECpd zl{=x+LsbMR_4P|&?s%p0^(ES`2n*`vD>^Vr^{p<}BWeM}oVxl&uk)`36~CtHoL62^ zL6!dLnu3< zhUkX_)8G{<_wmqXR@Ek;*BV&6la`*2AR)b{Y%=v!UGV|_lmk+~{Wdh=YA4$@FJ~rZ z9U11dbC;hHXyfF8p`ck|LW1>9hSd^(`A+2`JU4Z4w>X*vA&OmzReh;@x zI{o#WOtdMWI2pz18k}aR+y_9%(1B88D{veL(t(vFiK2zkKGF0$9HejAzpJ64!4eJS zbBhL2XD*bH>P{-2u5ixsi2qPjnqBK(-?mQv+;>)07D=RI>ubJnOb#fC&x1U7TU+00 z@F+~sBa=)@8IUb;A!uMSI0?l)!a6NsOTBJ>J8S@3(VhwauaC*Thy|Eu=5_QW*>C`r zc`UDVG|?k~@7vl;GelHU{{LVW7+3`1m0dWChOn-d=w28NwiHYhq7!D}~ zj{3P`54w0jj035#yv4oC_I#YzD;=khU#jiGwz%;dvc|K%_4x=s@Du_AAl{Y2?Zz^F z;P2pq>(FLVy%Oa31f+_nX_L#`W$n2Kw-srI0AzC$uY0I#pim}>@}5~49mbObbVv1( zm)j*T&Q1QY6?4036L8r#qohmQt+X>e|9&suz>=&DG;jBvS65pDLId9R%$l#I>BOz8 z5KS2zQ=TUSSlQ~oKr055@v0wMn=<{)3%(yz7!nOkPE>n6Q$xY1j#j3yw9-J4Qu(sCONUN!-G4#4#7n8*wJ~=wIWGFPAWJ~+jSIdQl>tpZe%^HIC7=Qj})Npop zh=kGyUZc5M`DaIMwJMSw_&(Ri^BSJ@{M6W4Ws9kejC(y)PlKW}R%P&z2?Vd1*`-!XDMsh0JYyre>rHKK?5erS?_ZMA3a$(ZN!sA7C&Uj=1jYc+ zV0b-N61}w)ja9OC=i{8I!GJ+%g)^f*WpfP;qcj%x zH6H1H@XRI7912bQB*Fron(HIotnRk9Hi!W)J-j@>zABo0w%o_73g@6()^Y5)iPq~z z&F!MQMqy>jW21P|8z5o8D+$fn55Avz%%8FudHO9ycJh-t$s~N)qoTYHdo39ppE7el zLx~;OmRkUF*mK82p#4|wxKKI+lR3&N_AP8$NE4~$cA=?`vXo59gWl%G+6%BQjjS#)_j5-}h>-M+VQv7!a+I0m#j{|>JTeV-%+&nD&j;r zY$QC3)zMhqM2A!fF&KR#mj^+wz2VoY8)Aevw{UE!hBW;%cgu6b6r?65jIUNF8aV3L zs33R}+W7GVZpD_My_AYqKa&6N5lbLfUApGd7 z?LV${;)8C>Kex?s8oniyK1;;Mi_6QQhnqk4mYos_$2jG1NqzV*Xe@ysn+p#AdTr@< F_doq!015yA literal 0 HcmV?d00001 diff --git a/dist-js/index.d.ts b/dist-js/index.d.ts new file mode 100644 index 0000000..6b38ccb --- /dev/null +++ b/dist-js/index.d.ts @@ -0,0 +1,95 @@ +export interface QueryResult { + /** The number of rows affected by the query. */ + rowsAffected: number; + /** + * The last inserted `id`. + * + * This value is always `0` when using the Postgres driver. If the + * last inserted id is required on Postgres, the `select` function + * must be used, with a `RETURNING` clause + * (`INSERT INTO todos (title) VALUES ($1) RETURNING id`). + */ + lastInsertId: number; +} +/** + * **Database** + * + * The `Database` class serves as the primary interface for + * communicating with the rust side of the sql plugin. + */ +export default class Database { + path: string; + constructor(path: string); + /** + * **load** + * + * A static initializer which connects to the underlying database and + * returns a `Database` instance once a connection to the database is established. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = await Database.load("sqlite:test.db"); + * ``` + */ + static load(path: string): Promise; + /** + * **get** + * + * A static initializer which synchronously returns an instance of + * the Database class while deferring the actual database connection + * until the first invocation or selection on the database. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = Database.get("sqlite:test.db"); + * ``` + */ + static get(path: string): Database; + /** + * **execute** + * + * Passes a SQL expression to the database for execution. + * + * @example + * ```ts + * const result = await db.execute( + * "UPDATE todos SET title = $1, completed = $2 WHERE id = $3", + * [ todos.title, todos.status, todos.id ] + * ); + * ``` + */ + execute(query: string, bindValues?: unknown[]): Promise; + /** + * **select** + * + * Passes in a SELECT query to the database for execution. + * + * @example + * ```ts + * const result = await db.select( + * "SELECT * from todos WHERE id = $1", id + * ); + * ``` + */ + select(query: string, bindValues?: unknown[]): Promise; + /** + * **close** + * + * Closes the database connection pool. + * + * @example + * ```ts + * const success = await db.close() + * ``` + * @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope. + */ + close(db?: string): Promise; +} diff --git a/dist-js/index.min.js b/dist-js/index.min.js new file mode 100644 index 0000000..f734a03 --- /dev/null +++ b/dist-js/index.min.js @@ -0,0 +1,119 @@ +var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0});}; + +var f={};e(f,{convertFileSrc:()=>w,invoke:()=>c,transformCallback:()=>s});function u(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s(e,r=!1){let n=u(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c(e,r={}){return new Promise((n,t)=>{let o=s(i=>{n(i),Reflect.deleteProperty(window,`_${a}`);},!0),a=s(i=>{t(i),Reflect.deleteProperty(window,`_${o}`);},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r});})}function w(e,r="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`} + +/** + * **Database** + * + * The `Database` class serves as the primary interface for + * communicating with the rust side of the sql plugin. + */ +class Database { + constructor(path) { + this.path = path; + } + /** + * **load** + * + * A static initializer which connects to the underlying database and + * returns a `Database` instance once a connection to the database is established. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = await Database.load("sqlite:test.db"); + * ``` + */ + static async load(path) { + const _path = await c("plugin:sql|load", { + db: path, + }); + return new Database(_path); + } + /** + * **get** + * + * A static initializer which synchronously returns an instance of + * the Database class while deferring the actual database connection + * until the first invocation or selection on the database. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = Database.get("sqlite:test.db"); + * ``` + */ + static get(path) { + return new Database(path); + } + /** + * **execute** + * + * Passes a SQL expression to the database for execution. + * + * @example + * ```ts + * const result = await db.execute( + * "UPDATE todos SET title = $1, completed = $2 WHERE id = $3", + * [ todos.title, todos.status, todos.id ] + * ); + * ``` + */ + async execute(query, bindValues) { + const [rowsAffected, lastInsertId] = await c("plugin:sql|execute", { + db: this.path, + query, + values: bindValues !== null && bindValues !== void 0 ? bindValues : [], + }); + return { + lastInsertId, + rowsAffected, + }; + } + /** + * **select** + * + * Passes in a SELECT query to the database for execution. + * + * @example + * ```ts + * const result = await db.select( + * "SELECT * from todos WHERE id = $1", id + * ); + * ``` + */ + async select(query, bindValues) { + const result = await c("plugin:sql|select", { + db: this.path, + query, + values: bindValues !== null && bindValues !== void 0 ? bindValues : [], + }); + return result; + } + /** + * **close** + * + * Closes the database connection pool. + * + * @example + * ```ts + * const success = await db.close() + * ``` + * @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope. + */ + async close(db) { + const success = await c("plugin:sql|close", { + db, + }); + return success; + } +} + +export { Database as default }; +//# sourceMappingURL=index.min.js.map diff --git a/dist-js/index.min.js.map b/dist-js/index.min.js.map new file mode 100644 index 0000000..d880b27 --- /dev/null +++ b/dist-js/index.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.min.js","sources":["../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-FEIY7W7S.js","../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-RCPA6UVN.js","../guest-js/index.ts"],"sourcesContent":["var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0})};export{e as a};\n","import{a as d}from\"./chunk-FEIY7W7S.js\";var f={};d(f,{convertFileSrc:()=>w,invoke:()=>c,transformCallback:()=>s});function u(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s(e,r=!1){let n=u(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c(e,r={}){return new Promise((n,t)=>{let o=s(i=>{n(i),Reflect.deleteProperty(window,`_${a}`)},!0),a=s(i=>{t(i),Reflect.deleteProperty(window,`_${o}`)},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r})})}function w(e,r=\"asset\"){let n=encodeURIComponent(e);return navigator.userAgent.includes(\"Windows\")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`}export{s as a,c as b,w as c,f as d};\n",null],"names":["d","invoke"],"mappings":"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","x_google_ignoreList":[0,1]} \ No newline at end of file diff --git a/dist-js/index.mjs b/dist-js/index.mjs new file mode 100644 index 0000000..e255900 --- /dev/null +++ b/dist-js/index.mjs @@ -0,0 +1,117 @@ +import { invoke } from '@tauri-apps/api/tauri'; + +/** + * **Database** + * + * The `Database` class serves as the primary interface for + * communicating with the rust side of the sql plugin. + */ +class Database { + constructor(path) { + this.path = path; + } + /** + * **load** + * + * A static initializer which connects to the underlying database and + * returns a `Database` instance once a connection to the database is established. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = await Database.load("sqlite:test.db"); + * ``` + */ + static async load(path) { + const _path = await invoke("plugin:sql|load", { + db: path, + }); + return new Database(_path); + } + /** + * **get** + * + * A static initializer which synchronously returns an instance of + * the Database class while deferring the actual database connection + * until the first invocation or selection on the database. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = Database.get("sqlite:test.db"); + * ``` + */ + static get(path) { + return new Database(path); + } + /** + * **execute** + * + * Passes a SQL expression to the database for execution. + * + * @example + * ```ts + * const result = await db.execute( + * "UPDATE todos SET title = $1, completed = $2 WHERE id = $3", + * [ todos.title, todos.status, todos.id ] + * ); + * ``` + */ + async execute(query, bindValues) { + const [rowsAffected, lastInsertId] = await invoke("plugin:sql|execute", { + db: this.path, + query, + values: bindValues !== null && bindValues !== void 0 ? bindValues : [], + }); + return { + lastInsertId, + rowsAffected, + }; + } + /** + * **select** + * + * Passes in a SELECT query to the database for execution. + * + * @example + * ```ts + * const result = await db.select( + * "SELECT * from todos WHERE id = $1", id + * ); + * ``` + */ + async select(query, bindValues) { + const result = await invoke("plugin:sql|select", { + db: this.path, + query, + values: bindValues !== null && bindValues !== void 0 ? bindValues : [], + }); + return result; + } + /** + * **close** + * + * Closes the database connection pool. + * + * @example + * ```ts + * const success = await db.close() + * ``` + * @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope. + */ + async close(db) { + const success = await invoke("plugin:sql|close", { + db, + }); + return success; + } +} + +export { Database as default }; +//# sourceMappingURL=index.mjs.map diff --git a/dist-js/index.mjs.map b/dist-js/index.mjs.map new file mode 100644 index 0000000..de31198 --- /dev/null +++ b/dist-js/index.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"index.mjs","sources":["../guest-js/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAgBA;;;;;AAKG;AACW,MAAO,QAAQ,CAAA;AAE3B,IAAA,WAAA,CAAY,IAAY,EAAA;AACtB,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;KAClB;AAED;;;;;;;;;;;;;;AAcG;AACH,IAAA,aAAa,IAAI,CAAC,IAAY,EAAA;AAC5B,QAAA,MAAM,KAAK,GAAG,MAAM,MAAM,CAAS,iBAAiB,EAAE;AACpD,YAAA,EAAE,EAAE,IAAI;AACT,SAAA,CAAC,CAAC;AAEH,QAAA,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC;KAC5B;AAED;;;;;;;;;;;;;;;AAeG;IACH,OAAO,GAAG,CAAC,IAAY,EAAA;AACrB,QAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;KAC3B;AAED;;;;;;;;;;;;AAYG;AACH,IAAA,MAAM,OAAO,CAAC,KAAa,EAAE,UAAsB,EAAA;QACjD,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,MAAM,MAAM,CAC/C,oBAAoB,EACpB;YACE,EAAE,EAAE,IAAI,CAAC,IAAI;YACb,KAAK;AACL,YAAA,MAAM,EAAE,UAAU,KAAA,IAAA,IAAV,UAAU,KAAV,KAAA,CAAA,GAAA,UAAU,GAAI,EAAE;AACzB,SAAA,CACF,CAAC;QAEF,OAAO;YACL,YAAY;YACZ,YAAY;SACb,CAAC;KACH;AAED;;;;;;;;;;;AAWG;AACH,IAAA,MAAM,MAAM,CAAI,KAAa,EAAE,UAAsB,EAAA;AACnD,QAAA,MAAM,MAAM,GAAG,MAAM,MAAM,CAAI,mBAAmB,EAAE;YAClD,EAAE,EAAE,IAAI,CAAC,IAAI;YACb,KAAK;AACL,YAAA,MAAM,EAAE,UAAU,KAAA,IAAA,IAAV,UAAU,KAAV,KAAA,CAAA,GAAA,UAAU,GAAI,EAAE;AACzB,SAAA,CAAC,CAAC;AAEH,QAAA,OAAO,MAAM,CAAC;KACf;AAED;;;;;;;;;;AAUG;IACH,MAAM,KAAK,CAAC,EAAW,EAAA;AACrB,QAAA,MAAM,OAAO,GAAG,MAAM,MAAM,CAAU,kBAAkB,EAAE;YACxD,EAAE;AACH,SAAA,CAAC,CAAC;AACH,QAAA,OAAO,OAAO,CAAC;KAChB;AACF;;;;"} \ No newline at end of file diff --git a/guest-js/index.ts b/guest-js/index.ts new file mode 100644 index 0000000..a574e72 --- /dev/null +++ b/guest-js/index.ts @@ -0,0 +1,140 @@ +import { invoke } from "@tauri-apps/api/tauri"; + +export interface QueryResult { + /** The number of rows affected by the query. */ + rowsAffected: number; + /** + * The last inserted `id`. + * + * This value is always `0` when using the Postgres driver. If the + * last inserted id is required on Postgres, the `select` function + * must be used, with a `RETURNING` clause + * (`INSERT INTO todos (title) VALUES ($1) RETURNING id`). + */ + lastInsertId: number; +} + +/** + * **Database** + * + * The `Database` class serves as the primary interface for + * communicating with the rust side of the sql plugin. + */ +export default class Database { + path: string; + constructor(path: string) { + this.path = path; + } + + /** + * **load** + * + * A static initializer which connects to the underlying database and + * returns a `Database` instance once a connection to the database is established. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = await Database.load("sqlite:test.db"); + * ``` + */ + static async load(path: string): Promise { + const _path = await invoke("plugin:sql|load", { + db: path, + }); + + return new Database(_path); + } + + /** + * **get** + * + * A static initializer which synchronously returns an instance of + * the Database class while deferring the actual database connection + * until the first invocation or selection on the database. + * + * # Sqlite + * + * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = Database.get("sqlite:test.db"); + * ``` + */ + static get(path: string): Database { + return new Database(path); + } + + /** + * **execute** + * + * Passes a SQL expression to the database for execution. + * + * @example + * ```ts + * const result = await db.execute( + * "UPDATE todos SET title = $1, completed = $2 WHERE id = $3", + * [ todos.title, todos.status, todos.id ] + * ); + * ``` + */ + async execute(query: string, bindValues?: unknown[]): Promise { + const [rowsAffected, lastInsertId] = await invoke<[number, number]>( + "plugin:sql|execute", + { + db: this.path, + query, + values: bindValues ?? [], + } + ); + + return { + lastInsertId, + rowsAffected, + }; + } + + /** + * **select** + * + * Passes in a SELECT query to the database for execution. + * + * @example + * ```ts + * const result = await db.select( + * "SELECT * from todos WHERE id = $1", id + * ); + * ``` + */ + async select(query: string, bindValues?: unknown[]): Promise { + const result = await invoke("plugin:sql|select", { + db: this.path, + query, + values: bindValues ?? [], + }); + + return result; + } + + /** + * **close** + * + * Closes the database connection pool. + * + * @example + * ```ts + * const success = await db.close() + * ``` + * @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope. + */ + async close(db?: string): Promise { + const success = await invoke("plugin:sql|close", { + db, + }); + return success; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a5607cd --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "tauri-plugin-sql-api", + "version": "0.0.0", + "description": "Interface with SQL databases", + "license": "MIT or APACHE-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "type": "module", + "browser": "dist-js/index.min.js", + "module": "dist-js/index.mjs", + "types": "dist-js/index.d.ts", + "exports": { + "import": "./dist-js/index.mjs", + "types": "./dist-js/index.d.ts", + "browser": "./dist-js/index.min.js" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "devDependencies": { + "tslib": "^2.5.0" + }, + "dependencies": { + "@tauri-apps/api": "^1.2.0" + } +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..6555e98 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; + +import { createConfig } from "../../shared/rollup.config.mjs"; + +export default createConfig({ + input: "guest-js/index.ts", + pkg: JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8") + ), + external: [/^@tauri-apps\/api/], +}); diff --git a/src/decode/mod.rs b/src/decode/mod.rs new file mode 100644 index 0000000..415c99b --- /dev/null +++ b/src/decode/mod.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "mysql")] +mod mysql; +#[cfg(feature = "postgres")] +mod postgres; +#[cfg(feature = "sqlite")] +mod sqlite; + +#[cfg(feature = "mysql")] +pub(crate) use mysql::to_json; + +#[cfg(feature = "postgres")] +pub(crate) use postgres::to_json; + +#[cfg(feature = "sqlite")] +pub(crate) use sqlite::to_json; diff --git a/src/decode/mysql.rs b/src/decode/mysql.rs new file mode 100644 index 0000000..e68bd1a --- /dev/null +++ b/src/decode/mysql.rs @@ -0,0 +1,90 @@ +use serde_json::Value as JsonValue; +use sqlx::{mysql::MySqlValueRef, TypeInfo, Value, ValueRef}; +use time::{Date, OffsetDateTime, PrimitiveDateTime, Time}; + +use crate::Error; + +pub(crate) fn to_json(v: MySqlValueRef) -> Result { + if v.is_null() { + return Ok(JsonValue::Null); + } + + let res = match v.type_info().name() { + "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode() { + JsonValue::String(v) + } else { + JsonValue::Null + } + } + "FLOAT" | "DOUBLE" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::from(v) + } else { + JsonValue::Null + } + } + "TINYINT" | "SMALLINT" | "INT" | "MEDIUMINT" | "BIGINT" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::Number(v.into()) + } else { + JsonValue::Null + } + } + "TINYINT UNSIGNED" | "SMALLINT UNSIGNED" | "INT UNSIGNED" | "MEDIUMINT UNSIGNED" + | "BIGINT UNSIGNED" | "YEAR" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::Number(v.into()) + } else { + JsonValue::Null + } + } + "BOOLEAN" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode() { + JsonValue::Bool(v) + } else { + JsonValue::Null + } + } + "DATE" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::String(v.to_string()) + } else { + JsonValue::Null + } + } + "TIME" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::