From 13ccad3f1881af965ad4281e6bde23eea57c5eb8 Mon Sep 17 00:00:00 2001 From: Pauline Date: Tue, 24 Oct 2023 20:48:39 -0400 Subject: [PATCH] feature(rust): initial work on rust (very broken) --- .gitignore | 2 + Cargo.toml | 48 +++++--- core/Cargo.toml | 16 +++ core/build.rs | 11 ++ .../20231024234944_init/migration.sql | 67 +++++++++++ core/prisma/migrations/migration_lock.toml | 3 + core/prisma/schema.prisma | 94 +++++++++++++++ core/src/lib.rs | 0 crates/generator/Cargo.toml | 18 +++ crates/generator/src/main.rs | 99 ++++++++++++++++ crates/generator/src/types/backend.rs | 12 ++ crates/generator/src/types/cli.rs | 42 +++++++ crates/generator/src/types/frontend.rs | 36 ++++++ crates/generator/src/types/mod.rs | 3 + crates/interpoly/Cargo.toml | 10 ++ crates/interpoly/src/lib.rs | 0 crates/prisma-cli/Cargo.toml | 13 +++ crates/prisma-cli/src/bin/prisma.rs | 3 + crates/prisma/.gitignore | 1 + crates/prisma/Cargo.toml | 15 +++ crates/prisma/src/lib.rs | 2 + crates/utils/Cargo.toml | 13 +++ crates/utils/src/lib.rs | 23 ++++ packages/client/.eslintrc.js | 8 ++ packages/client/package.json | 8 +- packages/client/src/core.ts | 14 +++ packages/client/src/form.ts | 79 +++++++++++++ packages/client/src/index.ts | 29 +++++ packages/client/src/rspc.tsx | 3 + packages/client/tsconfig.json | 9 ++ packages/ui/package.json | 1 + pnpm-lock.yaml | 110 ++++++++++++++---- rust-toolchain.toml | 3 + tsconfig.json | 12 ++ 34 files changed, 770 insertions(+), 37 deletions(-) create mode 100644 core/build.rs create mode 100644 core/prisma/migrations/20231024234944_init/migration.sql create mode 100644 core/prisma/migrations/migration_lock.toml create mode 100644 core/prisma/schema.prisma create mode 100644 core/src/lib.rs create mode 100644 crates/generator/Cargo.toml create mode 100644 crates/generator/src/main.rs create mode 100644 crates/generator/src/types/backend.rs create mode 100644 crates/generator/src/types/cli.rs create mode 100644 crates/generator/src/types/frontend.rs create mode 100644 crates/generator/src/types/mod.rs create mode 100644 crates/interpoly/Cargo.toml create mode 100644 crates/interpoly/src/lib.rs create mode 100644 crates/prisma-cli/Cargo.toml create mode 100644 crates/prisma-cli/src/bin/prisma.rs create mode 100644 crates/prisma/.gitignore create mode 100644 crates/prisma/Cargo.toml create mode 100644 crates/prisma/src/lib.rs create mode 100644 crates/utils/Cargo.toml create mode 100644 crates/utils/src/lib.rs create mode 100644 packages/client/.eslintrc.js create mode 100644 packages/client/src/core.ts create mode 100644 packages/client/src/form.ts create mode 100644 packages/client/src/index.ts create mode 100644 packages/client/src/rspc.tsx create mode 100644 packages/client/tsconfig.json create mode 100644 rust-toolchain.toml diff --git a/.gitignore b/.gitignore index 6efa02b..68e90da 100644 --- a/.gitignore +++ b/.gitignore @@ -729,3 +729,5 @@ apps/.deps .cargo/config.toml .cargo/config .github/scripts/deps +dev.db +dev.db-journal diff --git a/Cargo.toml b/Cargo.toml index dcf0f90..40578c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ resolver = "2" members = [ "core", "crates/*", - "apps/cli", - "apps/desktop/src-tauri" + # "apps/cli", + # "apps/desktop/src-tauri" ] [workspace.package] @@ -13,39 +13,59 @@ edition = "2021" repository = "https://github.com/Polyfrost/Nexus" documentation = "https://docs.polyfrost.org/nexus" readme = "README.md" -license-file = "LICENSE" homepage = "https://polyfrost.org" authors = ["Polyfrost"] [workspace.dependencies] -prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", features = [ - "rspc", +# prisma client rust dependencies +prisma-client-rust = { git = "https://github.com/pauliesnug/prisma-client-rust", features = [ "sqlite-create-many", "migrations", "sqlite", ], default-features = false } -prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", features = [ - "rspc", +prisma-client-rust-cli = { git = "https://github.com/pauliesnug/prisma-client-rust", features = [ "sqlite-create-many", "migrations", "sqlite", ], default-features = false } -prisma-client-rust-sdk = { git = "https://github.com/Brendonovich/prisma-client-rust", features = [ +prisma-client-rust-sdk = { git = "https://github.com/pauliesnug/prisma-client-rust", features = [ "sqlite", ], default-features = false } -rspc = { version = "1.0.0-rc.5" } +# rspc dependencies +rspc = { version = "1.0.0-rc.5", features = [ + "uuid", + "chrono" +] } specta = { version = "2.0.0-rc.6" } tauri-specta = { version = "2.0.0-rc.3" } + +# swift-rs for ios specific extensions swift-rs = { version = "1.0.6" } -tokio = { version = "1.33.0" } + +tracing = { git = "https://github.com/tokio-rs/tracing", rev = "f93cfa087e6ebdcbd8ecdcccca47d73c3a89ab94" } +tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "f93cfa087e6ebdcbd8ecdcccca47d73c3a89ab94", features = ["env-filter"] } +tracing-appender = { git = "https://github.com/tokio-rs/tracing", rev = "f93cfa087e6ebdcbd8ecdcccca47d73c3a89ab94" } + +# general use packages +tokio = { version = "1.33.0", features = ["full"] } uuid = { version = "1.4.1", features = ["v4", "serde"] } -serde = { version = "1.0" } +serde = { version = "1.0", features = ["derive"] } +reqwest = { version = "0.11", features = ["blocking", "json"] } +clap = { version = "4.4", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } +thiserror = { version = "1.0" } +async-stream = { version = "0.3" } +itertools = { version = "0.11" } +dashmap = { version = "5.5", features = ["serde"] } +regex = { version = "1.10" } +bytes = { version = "1.5" } +anyhow = { version = "1.0" } +cargo_metadata = { version = "0.18" } serde_json = { version = "1.0" } +lazy_static = { version = "1.4" } +futures = { version = "0.3" } -tracing = { git = "https://github.com/tokio-rs/tracing", rev = "29146260fb4615d271d2e899ad95a753bb42915e" } -tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "29146260fb4615d271d2e899ad95a753bb42915e", features = ["env-filter"] } -tracing-appender = { git = "https://github.com/tokio-rs/tracing", rev = "29146260fb4615d271d2e899ad95a753bb42915e" } [patch.crates-io] specta = { git = "https://github.com/oscartbeaumont/specta", rev = "22d4392b9296e761d12aebf9b50001235f4b2be7" } diff --git a/core/Cargo.toml b/core/Cargo.toml index e69de29..55ff945 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "nexuscore" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true + +[dependencies] +nexus-prisma = { path = "../crates/prisma" } +nexus-utils = { path = "../crates/utils"} + +tauri-specta.workspace = true diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 0000000..07af74d --- /dev/null +++ b/core/build.rs @@ -0,0 +1,11 @@ +use std::process::Command; + +fn main() { + let output = Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .expect("error getting git hash. Does `git rev-parse --short HEAD` work for you?"); + let git_hash = String::from_utf8(output.stdout) + .expect("Error passing output of `git rev-parse --short HEAD`"); + println!("cargo:rustc-env=GIT_HASH={git_hash}"); +} \ No newline at end of file diff --git a/core/prisma/migrations/20231024234944_init/migration.sql b/core/prisma/migrations/20231024234944_init/migration.sql new file mode 100644 index 0000000..b08963a --- /dev/null +++ b/core/prisma/migrations/20231024234944_init/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "instance" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "node_id" BLOB NOT NULL, + "node_name" TEXT NOT NULL, + "node_platform" INTEGER NOT NULL, + "last_run" DATETIME NOT NULL, + "date_created" DATETIME NOT NULL, + "timestamp" BIGINT +); + +-- CreateTable +CREATE TABLE "Statistics" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "java_version" TEXT NOT NULL DEFAULT '0', + "operating_system" INTEGER NOT NULL DEFAULT 0 +); + +-- CreateTable +CREATE TABLE "object" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "pub_id" BLOB NOT NULL, + "kind" INTEGER, + "date_created" DATETIME, + "date_accessed" DATETIME +); + +-- CreateTable +CREATE TABLE "job" ( + "id" BLOB NOT NULL PRIMARY KEY, + "name" TEXT, + "action" TEXT, + "status" INTEGER, + "errors_text" TEXT, + "data" BLOB, + "metadata" BLOB, + "parent_id" BLOB, + "task_count" INTEGER, + "completed_task_count" INTEGER, + "estimated_completion" DATETIME, + "date_created" DATETIME, + "date_started" DATETIME, + "date_completed" DATETIME, + CONSTRAINT "job_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "job" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "preference" ( + "key" TEXT NOT NULL PRIMARY KEY, + "value" BLOB +); + +-- CreateTable +CREATE TABLE "notification" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "read" BOOLEAN NOT NULL DEFAULT false, + "data" BLOB NOT NULL, + "expires_at" DATETIME +); + +-- CreateIndex +CREATE UNIQUE INDEX "instance_pub_id_key" ON "instance"("pub_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "object_pub_id_key" ON "object"("pub_id"); diff --git a/core/prisma/migrations/migration_lock.toml b/core/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..e5e5c47 --- /dev/null +++ b/core/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma new file mode 100644 index 0000000..f8b2927 --- /dev/null +++ b/core/prisma/schema.prisma @@ -0,0 +1,94 @@ +datasource db { + provider = "sqlite" + url = "file:dev.db" +} + +generator client { + provider = "cargo prisma" + output = "../../crates/prisma/src/prisma" + module_path = "prisma" + client_format = "folder" +} + +/// @local(id: pub_id) +model Instance { + id Int @id @default(autoincrement()) // This is NOT globally unique + pub_id Bytes @unique // This UUID exists solely for backwards compatibility. + + node_id Bytes + node_name String + node_platform Int + + last_run DateTime + date_created DateTime + + timestamp BigInt? + + @@map("instance") +} + +model Statistics { + id Int @id @default(autoincrement()) + date_captured DateTime @default(now()) + java_version String @default("0") + operating_system Int @default(0) +} + +/// @shared(id: pub_id) +model Object { + id Int @id @default(autoincrement()) + pub_id Bytes @unique + kind Int? + + date_created DateTime? + date_accessed DateTime? + + @@map("object") +} + +model Job { + id Bytes @id + + name String? + action String? + + status Int? + + errors_text String? + + data Bytes? + metadata Bytes? + + parent_id Bytes? + + task_count Int? + completed_task_count Int? + estimated_completion DateTime? + + date_created DateTime? + date_started DateTime? + date_completed DateTime? + + parent Job? @relation("jobs_dependency", fields: [parent_id], references: [id], onDelete: SetNull) + children Job[] @relation("jobs_dependency") + + @@map("job") +} + +/// @shared(id: key) +model Preference { + key String @id + value Bytes? + + @@map("preference") +} + +model Notification { + id Int @id @default(autoincrement()) + read Boolean @default(false) + + data Bytes + expires_at DateTime? + + @@map("notification") +} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/generator/Cargo.toml b/crates/generator/Cargo.toml new file mode 100644 index 0000000..3ceaad1 --- /dev/null +++ b/crates/generator/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "generator" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true + +[dependencies] +reqwest.workspace = true +clap.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +cargo_metadata.workspace = true diff --git a/crates/generator/src/main.rs b/crates/generator/src/main.rs new file mode 100644 index 0000000..a165a17 --- /dev/null +++ b/crates/generator/src/main.rs @@ -0,0 +1,99 @@ +use anyhow::Result; +use cargo_metadata::CargoOpt; +use clap::Parser; +use std::{fs::File, path::PathBuf}; +use types::backend::BackendDependency; +use types::cli::{Action, Arguments}; +use types::frontend::FrontendDependency; + +pub mod types; + +const FOSSA_NEXUS_URL: &str = + "https://app.fossa.com/api/revisions/git%2Bgithub.com%2Fpolyfrost%2Fnexus%24"; +const FOSSA_ONECONFIG_URL: &str = + "https://app.fossa.com/api/revisions/git%2Bgithub.com%2Fpolyfrost%2Foneconfig%24"; + +fn main() -> Result<()> { + let args = Arguments::parse(); + + match args.action { + Action::Frontend(sub_args) => write_frontend_deps(sub_args.revision, sub_args.path), + Action::Java(sub_args) => write_java_deps(sub_args.revision, sub_args.path), + Action::Backend(sub_args) => { + write_backend_deps(sub_args.manifest_path, sub_args.output_path) + } + } +} + +fn write_backend_deps(manifest_path: PathBuf, output_path: PathBuf) -> Result<()> { + let cmd = cargo_metadata::MetadataCommand::new() + .manifest_path(manifest_path) + .features(CargoOpt::AllFeatures) + .exec()?; + + let deps: Vec = cmd + .packages + .into_iter() + .filter_map(|p| { + (!cmd.workspace_members.iter().any(|t| &p.id == t)).then_some(BackendDependency { + title: p.name, + description: p.description, + url: p.repository, + version: p.version.to_string(), + authors: p.authors, + license: p.license, + }) + }) + .collect(); + + let mut file = File::create(output_path)?; + serde_json::to_writer(&mut file, &deps)?; + + Ok(()) +} + +fn write_frontend_deps(rev: String, path: PathBuf) -> Result<()> { + let url = format!("{FOSSA_NEXUS_URL}{rev}/dependencies"); + + let response = reqwest::blocking::get(url)?.text()?; + let json: Vec = serde_json::from_str(&response)?; + + let deps: Vec<_> = json + .into_iter() + .map(|dep| FrontendDependency { + title: dep.project.title, + authors: dep.project.authors, + description: dep.project.description, + url: dep.project.url, + license: dep.licenses, + }) + .collect(); + + let mut file = File::create(path)?; + serde_json::to_writer(&mut file, &deps)?; + + Ok(()) +} + +fn write_java_deps(rev: String, path: PathBuf) -> Result<()> { + let url = format!("{FOSSA_ONECONFIG_URL}{rev}/dependencies"); + + let response = reqwest::blocking::get(url)?.text()?; + let json: Vec = serde_json::from_str(&response)?; + + let deps: Vec<_> = json + .into_iter() + .map(|dep| FrontendDependency { + title: dep.project.title, + authors: dep.project.authors, + description: dep.project.description, + url: dep.project.url, + license: dep.licenses, + }) + .collect(); + + let mut file = File::create(path)?; + serde_json::to_writer(&mut file, &deps)?; + + Ok(()) +} diff --git a/crates/generator/src/types/backend.rs b/crates/generator/src/types/backend.rs new file mode 100644 index 0000000..9a2a707 --- /dev/null +++ b/crates/generator/src/types/backend.rs @@ -0,0 +1,12 @@ +use serde::{Serialize, Deserialize}; + +#[allow(clippy::module_name_repetitions)] +#[derive(Serialize, Deserialize)] +pub struct BackendDependency { + pub title: String, + pub description: Option, + pub url: Option, + pub version: String, + pub authors: Vec, + pub license: Option, +} \ No newline at end of file diff --git a/crates/generator/src/types/cli.rs b/crates/generator/src/types/cli.rs new file mode 100644 index 0000000..a33898c --- /dev/null +++ b/crates/generator/src/types/cli.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser)] +pub struct Arguments { + #[command(subcommand)] + pub action: Action, +} + +#[derive(Subcommand)] +pub enum Action { + Frontend(FrontendArgs), + Backend(BackendArgs), + Java(JavaArgs), +} + +#[derive(Args)] +pub struct FrontendArgs { + // could source this from `$GITHUB_SHA` for CI, if not set + #[arg(help = "the git revision")] + pub revision: String, + #[arg(help = "the output path")] + pub path: PathBuf, +} + +#[derive(Args)] +pub struct BackendArgs { + // could use `Cargo.toml` as the default from current dir (if not set) + #[arg(help = "path to the cargo manifest")] + pub manifest_path: PathBuf, + #[arg(help = "the output path")] + pub output_path: PathBuf, +} + +#[derive(Args)] +pub struct JavaArgs { + #[arg(help = "the git revision")] + pub revision: String, + #[arg(help = "the output path")] + pub path: PathBuf, +} diff --git a/crates/generator/src/types/frontend.rs b/crates/generator/src/types/frontend.rs new file mode 100644 index 0000000..2244f8d --- /dev/null +++ b/crates/generator/src/types/frontend.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Dependency { + pub project: Project, + pub licenses: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Project { + // pub locator: Option, + pub title: String, + pub description: Option, + pub url: Option, + pub authors: Vec>, +} + +#[derive(Serialize, Deserialize)] +pub struct License { + pub text: Option, + // pub license_id: Option, // always null AFAIK + pub copyright: Option, + // pub license_group_id: i64, + // pub ignored: bool, // always false from my testing + // pub revision_id: Option, +} + +#[allow(clippy::module_name_repetitions)] +#[derive(Serialize)] +pub struct FrontendDependency { + pub title: String, + pub description: Option, + pub url: Option, + pub authors: Vec>, + pub license: Vec, +} \ No newline at end of file diff --git a/crates/generator/src/types/mod.rs b/crates/generator/src/types/mod.rs new file mode 100644 index 0000000..8e51afb --- /dev/null +++ b/crates/generator/src/types/mod.rs @@ -0,0 +1,3 @@ +pub mod backend; +pub mod cli; +pub mod frontend; diff --git a/crates/interpoly/Cargo.toml b/crates/interpoly/Cargo.toml new file mode 100644 index 0000000..b092916 --- /dev/null +++ b/crates/interpoly/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "interpoly" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true diff --git a/crates/interpoly/src/lib.rs b/crates/interpoly/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/prisma-cli/Cargo.toml b/crates/prisma-cli/Cargo.toml new file mode 100644 index 0000000..2f78cc4 --- /dev/null +++ b/crates/prisma-cli/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "prisma-cli" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true + +[dependencies] +prisma-client-rust-cli.workspace = true diff --git a/crates/prisma-cli/src/bin/prisma.rs b/crates/prisma-cli/src/bin/prisma.rs new file mode 100644 index 0000000..236d6e6 --- /dev/null +++ b/crates/prisma-cli/src/bin/prisma.rs @@ -0,0 +1,3 @@ +fn main() { + prisma_client_rust_cli::run(); +} \ No newline at end of file diff --git a/crates/prisma/.gitignore b/crates/prisma/.gitignore new file mode 100644 index 0000000..4d20cf2 --- /dev/null +++ b/crates/prisma/.gitignore @@ -0,0 +1 @@ +src/*/ diff --git a/crates/prisma/Cargo.toml b/crates/prisma/Cargo.toml new file mode 100644 index 0000000..dabaa83 --- /dev/null +++ b/crates/prisma/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nexus-prisma" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true + +[dependencies] +prisma-client-rust.workspace = true +serde.workspace = true +serde_json.workspace = true diff --git a/crates/prisma/src/lib.rs b/crates/prisma/src/lib.rs new file mode 100644 index 0000000..a4041c7 --- /dev/null +++ b/crates/prisma/src/lib.rs @@ -0,0 +1,2 @@ +#[allow(warnings, unused)] +pub mod prisma; \ No newline at end of file diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml new file mode 100644 index 0000000..045a94e --- /dev/null +++ b/crates/utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nexus-utils" +version = "0.1.0" +license.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +readme.workspace = true +homepage.workspace = true +authors.workspace = true + +[dependencies] +uuid.workspace = true diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs new file mode 100644 index 0000000..7ef221b --- /dev/null +++ b/crates/utils/src/lib.rs @@ -0,0 +1,23 @@ +use uuid::Uuid; + +/// Combines an iterator of `T` and an iterator of `Option`, +/// removing any `None` values in the process +pub fn chain_optional_iter( + required: impl IntoIterator, + optional: impl IntoIterator>, +) -> Vec { + required + .into_iter() + .map(Some) + .chain(optional) + .flatten() + .collect() +} + +pub fn uuid_to_bytes(uuid: Uuid) -> Vec { + uuid.as_bytes().to_vec() +} + +pub fn from_bytes_to_uuid(bytes: &[u8]) -> Uuid { + Uuid::from_slice(bytes).expect("corrupted uuid in database") +} \ No newline at end of file diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js new file mode 100644 index 0000000..f80eaa9 --- /dev/null +++ b/packages/client/.eslintrc.js @@ -0,0 +1,8 @@ +/** @type {import('eslint').ESLint.ConfigData} */ +module.exports = { + extends: [require.resolve('@polyfrost/config/eslint/web.js')], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +}; diff --git a/packages/client/package.json b/packages/client/package.json index 877fb31..8bac7f2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -10,10 +10,14 @@ "typecheck": "tsc -b" }, "dependencies": { - "@rspc/client": "github:oscartbeaumont/rspc#b168e62", - "@rspc/react": "github:oscarbeaumont/rspc#b168e62", "@polyfrost/config": "workspace:*", + "@hookform/resolvers": "^3.3.2", + "@rspc/client": "^1.0.0-rc.3", + "@rspc/react-query": "^1.0.0-rc.4", + "@rspc/svelte-query": "^1.0.0-rc.4", + "@tanstack/query-core": "^5.0.5", "@tanstack/react-query": "^5.0.5", + "@tanstack/svelte-query": "^5.0.5", "plausible-tracker": "^0.3.8", "react-hook-form": "^7.47.0", "valtio": "^1.11.3", diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts new file mode 100644 index 0000000..73dca48 --- /dev/null +++ b/packages/client/src/core.ts @@ -0,0 +1,14 @@ +/* eslint-disable */ +// This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually. + +export type Procedures = { + queries: + { key: '', input: never, result: never } | + { key: 'test.me', input: never, result: number }, + mutations: + { key: '', input: never, result: null } | + { key: 'test.me', input: never, result: null }, + subscriptions: + { key: '', input: never, result: string[] } | + { key: 'test.me', input: never, result: null } +} diff --git a/packages/client/src/form.ts b/packages/client/src/form.ts new file mode 100644 index 0000000..5856804 --- /dev/null +++ b/packages/client/src/form.ts @@ -0,0 +1,79 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { useCallback, useRef } from 'react'; +import { useForm, UseFormProps } from 'react-hook-form'; +import { z } from 'zod'; + +type ZAny = z.ZodObject; +type ZAnyRecord = Record; + +export interface UseZodFormProps + extends Exclude>, 'resolver'> { + schema?: S; +} + +export const useZodForm = (props?: UseZodFormProps) => { + const { schema, ...formProps } = props ?? {}; + + return useForm>({ + ...formProps, + resolver: zodResolver(schema || z.object({})) + }); +}; + +type MultiDefaultValuesForm = { + [K in keyof S]?: UseZodFormProps['defaultValues']; +}; +type MultiFormsData = { [K in keyof S]?: z.infer }; +type MultiOnDataForm = (data: MultiFormsData) => any; +type MultiExcludeZodForm = Exclude< + UseZodFormProps, + 'schema' | 'defaultValues' +>; +type MultiOnValidForm = (data: MultiFormsData) => any | Promise; +type MultiOnErrorForm = (key: keyof S) => void; + +type MultiZodFormProps = { + schemas: S; + defaultValues: MultiDefaultValuesForm; + onData?: MultiOnDataForm; +}; + +export const useMultiZodForm = ({ + schemas, + defaultValues, + onData +}: MultiZodFormProps) => { + const formsData = useRef>({}); + + return { + useForm(key: K, props?: MultiExcludeZodForm) { + const form = useZodForm({ + ...props, + defaultValues: defaultValues[key], + schema: schemas[key] + }); + const handleSubmit = form.handleSubmit; + + form.handleSubmit = useCallback( + (onValid, onError) => + handleSubmit((data, e) => { + formsData.current[key] = data; + onData?.(formsData.current); + return onValid(data, e); + }, onError), + [handleSubmit, key] + ); + + return form; + }, + handleSubmit: (onValid: MultiOnValidForm, onError?: MultiOnErrorForm) => () => { + for (const key of Object.keys(schemas)) + if (formsData.current[key] === undefined) { + onError?.(key); + return; + } + + return onValid(formsData.current); + } + }; +}; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts new file mode 100644 index 0000000..415dd3b --- /dev/null +++ b/packages/client/src/index.ts @@ -0,0 +1,29 @@ +import { Link } from '@rspc/client'; + +declare global { + // eslint-disable-next-line + var isDev: boolean; + // eslint-disable-next-line + var rspcLinks: Link[]; +} + +if ( + globalThis.localStorage === undefined || + globalThis.isDev === undefined || + globalThis.rspcLinks === undefined +) + throw new Error('patch `globalThis` before importing `@polyfrost/client`'); + +declare global { + // Temporary Tauri patch to avoid returning a Promise. + // export function confirm(): never; // boolean | Promise; + export function confirm(): boolean | Promise; +} + +export * from './hooks'; +export * from './stores'; +export * from './rspc'; +export * from './core'; +export * from './utils'; +export * from './lib'; +export * from './form'; diff --git a/packages/client/src/rspc.tsx b/packages/client/src/rspc.tsx new file mode 100644 index 0000000..5fdc4da --- /dev/null +++ b/packages/client/src/rspc.tsx @@ -0,0 +1,3 @@ +import { ProcedureDef } from '@rspc/client'; +import { RSPCError, initRspc } from '@rspc/client'; +import { QueryClient } from '@tanstack/react-query'; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..41d393a --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../config/base.tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "./dist", + "emitDeclarationOnly": false + }, + "include": ["src"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json index d843ba8..45ece82 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -19,6 +19,7 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@headlessui/tailwindcss": "^0.2.0", + "@hookform/resolvers": "^3.3.2", "@polyfrost/assets": "workspace:*", "@react-spring/web": "^9.7.3", "@tailwindcss/forms": "^0.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d7747c..b01bb37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,14 +171,17 @@ importers: specifier: workspace:* version: link:../config '@rspc/client': - specifier: '=0.0.0-main-799eec5d' - version: 0.0.0-main-799eec5d - '@rspc/react': - specifier: '=0.0.0-main-799eec5d' - version: 0.0.0-main-799eec5d(@rspc/client@0.0.0-main-799eec5d)(@tanstack/react-query@5.0.5)(react@18.2.0) + specifier: ^1.0.0-rc.3 + version: 1.0.0-rc.3 + '@tanstack/query-core': + specifier: ^5.0.5 + version: 5.0.5 '@tanstack/react-query': specifier: ^5.0.5 version: 5.0.5(react-dom@18.2.0)(react@18.2.0) + '@tanstack/svelte-query': + specifier: ^5.0.5 + version: 5.0.5(svelte@4.2.2) plausible-tracker: specifier: ^0.3.8 version: 0.3.8 @@ -258,6 +261,9 @@ importers: '@headlessui/tailwindcss': specifier: ^0.2.0 version: 0.2.0(tailwindcss@3.3.3) + '@hookform/resolvers': + specifier: ^3.3.2 + version: 3.3.2(react-hook-form@7.47.0) '@polyfrost/assets': specifier: workspace:* version: link:../assets @@ -2258,6 +2264,14 @@ packages: tailwindcss: 3.3.3 dev: false + /@hookform/resolvers@3.3.2(react-hook-form@7.47.0): + resolution: {integrity: sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.47.0(react@18.2.0) + dev: false + /@humanwhocodes/config-array@0.11.13: resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} @@ -3203,20 +3217,8 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 - /@rspc/client@0.0.0-main-799eec5d: - resolution: {integrity: sha512-vLE2bpRh2GblwPhzORi4Iv3EP86aS3oDegY/Km0kQrdc60jWzcy39YBS/ftlcjOFqXc1Hu0z4uLwj0UmJ/payA==} - dev: false - - /@rspc/react@0.0.0-main-799eec5d(@rspc/client@0.0.0-main-799eec5d)(@tanstack/react-query@5.0.5)(react@18.2.0): - resolution: {integrity: sha512-O3kHvBLuB2FG1Mq86aV3J5z7MEk+CnvgxjqnAw13IwGJyuERm+YdS42XLOT8gRZ06lXAmwJYwqSXarj4hosILw==} - peerDependencies: - '@rspc/client': 0.0.0-main-799eec5d - '@tanstack/react-query': ^4.26.0 - react: ^18.2.0 - dependencies: - '@rspc/client': 0.0.0-main-799eec5d - '@tanstack/react-query': 5.0.5(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 + /@rspc/client@1.0.0-rc.3: + resolution: {integrity: sha512-od5M5qVi3BKaX/uvyUeuIAd26H7SGahyV4Tb4I3hKoqnKBlqp5yE6K/x0OlNFTW9Obfw8SIsf+xLXtT2Pid0qg==} dev: false /@sinclair/typebox@0.27.8: @@ -4343,6 +4345,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@tanstack/svelte-query@5.0.5(svelte@4.2.2): + resolution: {integrity: sha512-NYdxt4oqlduR1S2W1ROc16H2hux4PHHbs27xu75AvgHDEv6BTJOfXODpG/WTLBAkgfWT38Y9a3TXsnutfxjGvg==} + peerDependencies: + svelte: '>=3 <5' + dependencies: + '@tanstack/query-core': 5.0.5 + svelte: 4.2.2 + dev: false + /@testing-library/dom@9.3.3: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} engines: {node: '>=14'} @@ -5158,7 +5169,6 @@ packages: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 - dev: true /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} @@ -5404,7 +5414,6 @@ packages: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: dequal: 2.0.3 - dev: true /b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} @@ -5821,6 +5830,16 @@ packages: engines: {node: '>=6'} dev: false + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.3 + acorn: 8.10.0 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -6043,6 +6062,14 @@ packages: nth-check: 2.1.1 dev: true + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: false + /css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -8101,6 +8128,12 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.3 + dev: false + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -8518,6 +8551,10 @@ packages: json5: 2.2.3 dev: false + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: false + /locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -8817,6 +8854,10 @@ packages: '@types/mdast': 3.0.14 dev: false + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: false + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -9731,6 +9772,14 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.3 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -11295,6 +11344,25 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /svelte@4.2.2: + resolution: {integrity: sha512-My2tytF2e2NnHSpn2M7/3VdXT4JdTglYVUuSuK/mXL2XtulPYbeBfl8Dm1QiaKRn0zoULRnL+EtfZHHP0k4H3A==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + acorn: 8.10.0 + aria-query: 5.3.0 + axobject-query: 3.2.1 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.5 + periscopic: 3.1.0 + dev: false + /svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} dev: true diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..0d37b20 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +profile = "default" +channel = "nightly" diff --git a/tsconfig.json b/tsconfig.json index fbbf698..cc36f54 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,17 @@ { "references": [ + { + "path": "apps/desktop" + }, + { + "path": "apps/website" + }, + { + "path": "interface" + }, + { + "path": "packages/client" + }, { "path": "packages/ui" }