lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit d3da6167b3370e46d00ada7f285f46da7b104d5d
parent 08d6985262ecd4e040b27468679e6d50a863a05d
Author: triesap <tyson@radroots.org>
Date:   Wed, 20 Aug 2025 15:04:21 -0700

workspace: add `crates/*` workspace with `radroots-core`, `radroots-events`, `radroots-events-codec`

Diffstat:
M.gitignore | 61++++++++++++++++++++++++++++++++++++++++++++++++++++---------
MCargo.lock | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
MCargo.toml | 17+++++------------
Dbindings/ts/package.json | 46----------------------------------------------
Dbindings/ts/src/events/schema.ts | 133-------------------------------------------------------------------------------
Dbindings/ts/src/events/types.ts | 17-----------------
Dbindings/ts/src/index.ts | 3---
Dbindings/ts/src/types.ts | 95-------------------------------------------------------------------------------
Dbindings/ts/yarn.lock | 992-------------------------------------------------------------------------------
Acrates/core/Cargo.toml | 18++++++++++++++++++
Acrates/core/src/currency.rs | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/decimal.rs | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/discount.rs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/lib.rs | 24++++++++++++++++++++++++
Acrates/core/src/money.rs | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/percent.rs | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/quantity.rs | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/quantity_price.rs | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/core/src/serde_ext.rs | 23+++++++++++++++++++++++
Acrates/core/src/unit.rs | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/Cargo.toml | 19+++++++++++++++++++
Acrates/events-codec/src/job/encode.rs | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/error.rs | 42++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/feedback/decode.rs | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/feedback/encode.rs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/mod.rs | 20++++++++++++++++++++
Acrates/events-codec/src/job/request/decode.rs | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/request/encode.rs | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/result/decode.rs | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/result/encode.rs | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/traits.rs | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/job/util.rs | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-codec/src/lib.rs | 5+++++
Acrates/events-indexed/Cargo.toml | 17+++++++++++++++++
Acrates/events-indexed/src/checkpoint.rs | 47+++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-indexed/src/lib.rs | 15+++++++++++++++
Acrates/events-indexed/src/manifest.rs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events-indexed/src/serde_ext.rs | 17+++++++++++++++++
Acrates/events-indexed/src/types.rs | 21+++++++++++++++++++++
Acrates/events/Cargo.toml | 21+++++++++++++++++++++
Acrates/events/src/comment/models.rs | 27+++++++++++++++++++++++++++
Acrates/events/src/follow/models.rs | 33+++++++++++++++++++++++++++++++++
Acrates/events/src/job/feedback/models.rs | 35+++++++++++++++++++++++++++++++++++
Acrates/events/src/job/mod.rs | 41+++++++++++++++++++++++++++++++++++++++++
Acrates/events/src/job/request/models.rs | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events/src/job/result/models.rs | 35+++++++++++++++++++++++++++++++++++
Acrates/events/src/kinds.rs | 38++++++++++++++++++++++++++++++++++++++
Acrates/events/src/lib.rs | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events/src/listing/models.rs | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/events/src/profile/models.rs | 33+++++++++++++++++++++++++++++++++
Acrates/events/src/reaction/models.rs | 25+++++++++++++++++++++++++
Acrates/events/src/tag.rs | 3+++
Acrates/trade/Cargo.toml | 19+++++++++++++++++++
Acrates/trade/src/lib.rs | 6++++++
Acrates/trade/src/listing/kinds.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/meta.rs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/mod.rs | 15+++++++++++++++
Acrates/trade/src/listing/model.rs | 22++++++++++++++++++++++
Acrates/trade/src/listing/price_ext.rs | 44++++++++++++++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/stage/accept.rs | 19+++++++++++++++++++
Acrates/trade/src/listing/stage/conveyance.rs | 41+++++++++++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/stage/fulfillment.rs | 34++++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/stage/invoice.rs | 19+++++++++++++++++++
Acrates/trade/src/listing/stage/order.rs | 33+++++++++++++++++++++++++++++++++
Acrates/trade/src/listing/stage/payment.rs | 31+++++++++++++++++++++++++++++++
Acrates/trade/src/listing/stage/receipt.rs | 18++++++++++++++++++
Acrates/trade/src/listing/tags.rs | 38++++++++++++++++++++++++++++++++++++++
Acrates/trade/src/prelude.rs | 1+
Apackage.json | 19+++++++++++++++++++
Apackages/bindings/core/package.json | 43+++++++++++++++++++++++++++++++++++++++++++
Apackages/bindings/core/src/index.ts | 1+
Apackages/bindings/core/src/types.ts | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rbindings/ts/tsconfig.cjs.json -> packages/bindings/core/tsconfig.cjs.json | 0
Rbindings/ts/tsconfig.esm.json -> packages/bindings/core/tsconfig.esm.json | 0
Rbindings/ts/tsconfig.json -> packages/bindings/core/tsconfig.json | 0
Apackages/bindings/events-indexed/package.json | 43+++++++++++++++++++++++++++++++++++++++++++
Apackages/bindings/events-indexed/src/index.ts | 1+
Apackages/bindings/events-indexed/src/types.ts | 42++++++++++++++++++++++++++++++++++++++++++
Rbindings/ts/tsconfig.cjs.json -> packages/bindings/events-indexed/tsconfig.cjs.json | 0
Rbindings/ts/tsconfig.esm.json -> packages/bindings/events-indexed/tsconfig.esm.json | 0
Rbindings/ts/tsconfig.json -> packages/bindings/events-indexed/tsconfig.json | 0
Apackages/bindings/events/package.json | 44++++++++++++++++++++++++++++++++++++++++++++
Apackages/bindings/events/src/index.ts | 3+++
Apackages/bindings/events/src/lib.ts | 4++++
Apackages/bindings/events/src/schemas.ts | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/bindings/events/src/types.ts | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rbindings/ts/tsconfig.cjs.json -> packages/bindings/events/tsconfig.cjs.json | 0
Rbindings/ts/tsconfig.esm.json -> packages/bindings/events/tsconfig.esm.json | 0
Rbindings/ts/tsconfig.json -> packages/bindings/events/tsconfig.json | 0
Apackages/bindings/trade/package.json | 45+++++++++++++++++++++++++++++++++++++++++++++
Apackages/bindings/trade/src/index.ts | 2++
Apackages/bindings/trade/src/lib.ts | 8++++++++
Apackages/bindings/trade/src/types.ts | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rbindings/ts/tsconfig.cjs.json -> packages/bindings/trade/tsconfig.cjs.json | 0
Rbindings/ts/tsconfig.esm.json -> packages/bindings/trade/tsconfig.esm.json | 0
Rbindings/ts/tsconfig.json -> packages/bindings/trade/tsconfig.json | 0
Dprompt.txt | 115-------------------------------------------------------------------------------
Dsrc/events/comment/models.rs | 26--------------------------
Dsrc/events/follow/models.rs | 32--------------------------------
Dsrc/events/lib.rs | 10----------
Dsrc/events/listing/models.rs | 110-------------------------------------------------------------------------------
Dsrc/events/mod.rs | 32--------------------------------
Dsrc/events/profile/models.rs | 33---------------------------------
Dsrc/events/reaction/models.rs | 25-------------------------
Dsrc/lib.rs | 7-------
Dsrc/models/indexer.rs | 26--------------------------
Dsrc/models/mod.rs | 1-
Aturbo.json | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
108 files changed, 4651 insertions(+), 1756 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,14 +1,58 @@ -/target +# Dependencies node_modules -dist +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo .turbo -justfile -notes*.txt -.tmp* -.vscode -git-diff.txt +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +.yarn +target/ +yarn.lock + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# OS .DS_Store +Thumbs.db + +#secrets *.pem +*.crt +*.key -#bindings/**/*.ts -\ No newline at end of file +# local +.tmp* +.archive* +.dev* +.local* +.vscode +notes*.txt +notes*.md +notes*.json +tree*.txt +git-diff*.txt +prompt*.txt +tree*.txt +justfile diff --git a/Cargo.lock b/Cargo.lock @@ -18,6 +18,12 @@ dependencies = [ ] [[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -31,9 +37,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cc" -version = "1.2.31" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] @@ -106,9 +112,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" @@ -139,9 +145,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -156,20 +162,80 @@ dependencies = [ ] [[package]] -name = "radroots-common" +name = "radroots-core" version = "0.1.0" dependencies = [ + "rust_decimal", + "rust_decimal_macros", + "serde", + "typeshare", +] + +[[package]] +name = "radroots-events" +version = "0.1.0" +dependencies = [ + "radroots-core", "serde", "serde_json", - "thiserror", "typeshare", ] [[package]] +name = "radroots-events-codec" +version = "0.1.0" +dependencies = [ + "radroots-core", + "radroots-events", + "serde", + "serde_json", +] + +[[package]] +name = "radroots-events-indexed" +version = "0.1.0" +dependencies = [ + "serde", + "typeshare", +] + +[[package]] +name = "radroots-trade" +version = "0.1.0" +dependencies = [ + "radroots-core", + "radroots-events", + "radroots-events-codec", + "serde", + "typeshare", +] + +[[package]] +name = "rust_decimal" +version = "1.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +dependencies = [ + "arrayvec", + "num-traits", + "serde", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6268b74858287e1a062271b988a0c534bf85bbeb567fe09331bf40ed78113d5" +dependencies = [ + "quote", + "syn", +] + +[[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -217,9 +283,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -227,26 +293,6 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "typeshare" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -1,12 +1,5 @@ -[package] -name = "radroots-common" -version = "0.1.0" -authors = ["Radroots Authors"] -license = "AGPLv3" -edition = "2021" - -[dependencies] -serde = "1.0" -serde_json = "1.0" -thiserror = "1.0" -typeshare = "1.0.0" +[workspace] +members = [ + "crates/*", +] +resolver = "2" diff --git a/bindings/ts/package.json b/bindings/ts/package.json @@ -1,45 +0,0 @@ -{ - "name": "@radroots/radroots-common-bindings", - "version": "1.0.0", - "private": true, - "license": "AGPLv3", - "type": "module", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", - "types": "./dist/types/index.d.ts", - "exports": { - ".": { - "types": "./dist/types/index.d.ts", - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" - } - }, - "files": [ - "dist" - ], - "sideEffects": false, - "scripts": { - "build:esm": "tsc -p tsconfig.esm.json", - "build:cjs": "tsc -p tsconfig.cjs.json", - "build": "npm run clean && npm run build:esm && npm run build:cjs", - "prebuild": "npm run clean", - "clean": "rimraf dist", - "dev": "npm run watch", - "watch": "tsc -w", - "gen:types": "typeshare --lang typescript --output-file=src/types.ts ../../src", - "gen:exports": "gen-package-exports.js --is_module", - "gen": "npm run gen:types && npm run gen:exports" - }, - "devDependencies": { - "@radroots/dev": "*", - "@radroots/tsconfig": "*", - "rimraf": "^6.0.1", - "ts-to-zod": "^3.15.0" - }, - "dependencies": { - "zod": "^4.0.5" - }, - "publishConfig": { - "access": "public" - } -} -\ No newline at end of file diff --git a/bindings/ts/src/events/schema.ts b/bindings/ts/src/events/schema.ts @@ -1,133 +0,0 @@ -import { z } from "zod"; - -export const radroots_nostr_event_ref_schema = z.object({ - id: z.string(), - author: z.string(), - kind: z.number(), - d_tag: z.string().optional(), - relays: z.array(z.string()).optional() -}); - -export const radroots_listing_image_schema = z.object({ - url: z.string(), - size: z.object({ - w: z.number(), - h: z.number() - }).optional() -}); - -export const radroots_listing_location_schema = z.object({ - primary: z.string(), - city: z.string().optional(), - region: z.string().optional(), - country: z.string().optional(), - lat: z.number().optional(), - lng: z.number().optional(), - geohash: z.string().optional() -}); - -export const radroots_listing_discount_schema = z.union([ - z.object({ - quantity: z.object({ - ref_quantity: z.string(), - threshold: z.string(), - value: z.string(), - currency: z.string() - }) - }), - z.object({ - mass: z.object({ - unit: z.string(), - threshold: z.string(), - threshold_unit: z.string(), - value: z.string(), - currency: z.string() - }) - }), - z.object({ - subtotal: z.object({ - threshold: z.string(), - currency: z.string(), - value: z.string(), - measure: z.string() - }) - }), - z.object({ - total: z.object({ - total_min: z.string(), - value: z.string(), - measure: z.string() - }) - }) -]); - -export const radroots_listing_price_schema = z.object({ - amt: z.string(), - currency: z.string(), - qty_amt: z.string(), - qty_unit: z.string(), - qty_key: z.string() -}); - -export const radroots_listing_quantity_schema = z.object({ - amt: z.string(), - unit: z.string(), - label: z.string().optional() -}); - -export const radroots_listing_product_schema = z.object({ - key: z.string(), - title: z.string(), - category: z.string(), - summary: z.string().optional(), - process: z.string().optional(), - lot: z.string().optional(), - location: z.string().optional(), - profile: z.string().optional(), - year: z.string().optional() -}); - -export const radroots_listing_schema = z.object({ - d_tag: z.string(), - product: radroots_listing_product_schema, - quantities: z.array(radroots_listing_quantity_schema), - prices: z.array(radroots_listing_price_schema), - discounts: z.array(radroots_listing_discount_schema).optional(), - location: radroots_listing_location_schema.optional(), - images: z.array(radroots_listing_image_schema).optional() -}); - -export const radroots_profile_schema = z.object({ - name: z.string(), - display_name: z.string().optional(), - nip05: z.string().optional(), - about: z.string().optional(), - website: z.string().optional(), - picture: z.string().optional(), - banner: z.string().optional(), - lud06: z.string().optional(), - lud16: z.string().optional(), - bot: z.string().optional() -}); - -export const radroots_comment_schema = z.object({ - root: radroots_nostr_event_ref_schema, - parent: radroots_nostr_event_ref_schema, - content: z.string() -}); - -export const radroots_reaction_schema = z.object({ - root: radroots_nostr_event_ref_schema, - content: z.string() -}); - -export const radroots_follow_profile_schema = z.object({ - published_at: z.number(), - public_key: z.string(), - relay_url: z.string().optional(), - contact_name: z.string().optional() -}); - -export const radroots_follow_schema = z.object({ - list: z.array(radroots_follow_profile_schema) -}); diff --git a/bindings/ts/src/events/types.ts b/bindings/ts/src/events/types.ts @@ -1,16 +0,0 @@ -import { z } from "zod"; -import { radroots_comment_schema, radroots_follow_profile_schema, radroots_follow_schema, radroots_listing_discount_schema, radroots_listing_image_schema, radroots_listing_location_schema, radroots_listing_price_schema, radroots_listing_product_schema, radroots_listing_quantity_schema, radroots_listing_schema, radroots_nostr_event_ref_schema, radroots_profile_schema, radroots_reaction_schema } from "./schema.js"; - -export type RadrootsNostrEventRef = z.infer<typeof radroots_nostr_event_ref_schema>; -export type RadrootsListingImage = z.infer<typeof radroots_listing_image_schema>; -export type RadrootsListingLocation = z.infer<typeof radroots_listing_location_schema>; -export type RadrootsListingDiscount = z.infer<typeof radroots_listing_discount_schema>; -export type RadrootsListingPrice = z.infer<typeof radroots_listing_price_schema>; -export type RadrootsListingQuantity = z.infer<typeof radroots_listing_quantity_schema>; -export type RadrootsListingProduct = z.infer<typeof radroots_listing_product_schema>; -export type RadrootsListing = z.infer<typeof radroots_listing_schema>; -export type RadrootsProfile = z.infer<typeof radroots_profile_schema>; -export type RadrootsComment = z.infer<typeof radroots_comment_schema>; -export type RadrootsReaction = z.infer<typeof radroots_reaction_schema>; -export type RadrootsFollowProfile = z.infer<typeof radroots_follow_profile_schema>; -export type RadrootsFollow = z.infer<typeof radroots_follow_schema>; -\ No newline at end of file diff --git a/bindings/ts/src/index.ts b/bindings/ts/src/index.ts @@ -1,3 +0,0 @@ -export * from "./events/schema.js" -export * from "./events/types.js" -export * from "./types.js" diff --git a/bindings/ts/src/types.ts b/bindings/ts/src/types.ts @@ -1,95 +0,0 @@ -import { RadrootsComment, RadrootsFollow, RadrootsListing, RadrootsProfile, RadrootsReaction } from "./events/types.js"; - -/* - Generated by typeshare 1.13.2 -*/ - -export interface RadrootsNostrEvent { - id: string; - author: string; - created_at: number; - kind: number; - tags: string[][]; - content: string; - sig: string; -} - -export interface RadrootsCommentEventMetadata { - id: string; - author: string; - published_at: number; - comment: RadrootsComment; -} - -export interface RadrootsCommentEventIndex { - event: RadrootsNostrEvent; - metadata: RadrootsCommentEventMetadata; -} - -export interface RadrootsFollowEventMetadata { - id: string; - author: string; - published_at: number; - follow: RadrootsFollow; -} - -export interface RadrootsFollowEventIndex { - event: RadrootsNostrEvent; - metadata: RadrootsFollowEventMetadata; -} - -export interface RadrootsIndexShardMetadata { - file: string; - count: number; - first_id: string; - last_id: string; - first_published_at: number; - last_published_at: number; - sha256: string; -} - -export interface RadrootsIndexManifest { - country: string; - total: number; - shard_size: number; - first_published_at: number; - last_published_at: number; - shards: RadrootsIndexShardMetadata[]; -} - -export interface RadrootsListingEventMetadata { - id: string; - author: string; - published_at: number; - listing: RadrootsListing; -} - -export interface RadrootsListingEventIndex { - event: RadrootsNostrEvent; - metadata: RadrootsListingEventMetadata; -} - -export interface RadrootsProfileEventMetadata { - id: string; - author: string; - published_at: number; - profile: RadrootsProfile; -} - -export interface RadrootsProfileEventIndex { - event: RadrootsNostrEvent; - metadata: RadrootsProfileEventMetadata; -} - -export interface RadrootsReactionEventMetadata { - id: string; - author: string; - published_at: number; - reaction: RadrootsReaction; -} - -export interface RadrootsReactionEventIndex { - event: RadrootsNostrEvent; - metadata: RadrootsReactionEventMetadata; -} - diff --git a/bindings/ts/yarn.lock b/bindings/ts/yarn.lock @@ -1,992 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@oclif/core@>=3.26.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@oclif/core/-/core-4.5.0.tgz#0163f933098bfa52f86387f11900da1ad13235d3" - integrity sha512-UYWyDFNKFyzgXVXO0DHfOvJ/8qpw4yPYe7fOHausDEVU44qjDr90ZnfYTljZPK8dhgMggxiZs9n+TFajnXRp7g== - dependencies: - ansi-escapes "^4.3.2" - ansis "^3.17.0" - clean-stack "^3.0.1" - cli-spinners "^2.9.2" - debug "^4.4.0" - ejs "^3.1.10" - get-package-type "^0.1.0" - indent-string "^4.0.0" - is-wsl "^2.2.0" - lilconfig "^3.1.3" - minimatch "^9.0.5" - semver "^7.6.3" - string-width "^4.2.3" - supports-color "^8" - tinyglobby "^0.2.14" - widest-line "^3.1.0" - wordwrap "^1.0.0" - wrap-ansi "^7.0.0" - -"@typescript/vfs@^1.5.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@typescript/vfs/-/vfs-1.6.1.tgz#fe7087d5a43715754f7ea9bf6e0b905176c9eebd" - integrity sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA== - dependencies: - debug "^4.1.1" - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -ansis@^3.17.0: - version "3.17.0" - resolved "https://registry.yarnpkg.com/ansis/-/ansis-3.17.0.tgz#fa8d9c2a93fe7d1177e0c17f9eeb562a58a832d7" - integrity sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg== - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== - dependencies: - balanced-match "^1.0.0" - -braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -callsites@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -case@^1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" - integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== - -chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -chokidar@^3.5.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -clean-stack@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" - integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== - dependencies: - escape-string-regexp "4.0.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0, cli-spinners@^2.9.2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -cross-spawn@^7.0.6: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@^4.1.1, debug@^4.2.0, debug@^4.4.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" - integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== - dependencies: - ms "^2.1.3" - -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -ejs@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== - dependencies: - jake "^10.8.5" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -escape-string-regexp@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -esm@^3.2.25: - version "3.2.25" - resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" - integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fdir@^6.4.4: - version "6.4.6" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.6.tgz#2b268c0232697063111bbf3f64810a2a741ba281" - integrity sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -filelist@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" - integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== - dependencies: - minimatch "^5.0.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -foreground-child@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - -fs-extra@^11.1.1: - version "11.3.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" - integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^11.0.0: - version "11.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" - integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== - dependencies: - foreground-child "^3.3.1" - jackspeak "^4.1.1" - minimatch "^10.0.3" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^2.0.0" - -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inquirer@^8.2.0: - version "8.2.6" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" - integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.5.5" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - wrap-ansi "^6.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-observable@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-2.1.0.tgz#5c8d733a0b201c80dff7bb7c0df58c6a255c7c69" - integrity sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -jackspeak@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" - integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - -jake@^10.8.5: - version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" - integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== - dependencies: - async "^3.2.3" - chalk "^4.0.2" - filelist "^1.0.4" - minimatch "^3.1.2" - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -lilconfig@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" - integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== - -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -lru-cache@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" - integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimatch@^10.0.3: - version "10.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa" - integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - -minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -observable-fns@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/observable-fns/-/observable-fns-0.6.1.tgz#636eae4fdd1132e88c0faf38d33658cc79d87e37" - integrity sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg== - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -ora@^5.4.0, ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-scurry@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" - integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== - dependencies: - lru-cache "^11.0.0" - minipass "^7.1.2" - -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -prettier@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" - integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== - -readable-stream@^3.4.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -rimraf@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e" - integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A== - dependencies: - glob "^11.0.0" - package-json-from-dist "^1.0.0" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -rxjs@^7.4.0, rxjs@^7.5.5: - version "7.8.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^7.6.3: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -threads@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/threads/-/threads-1.7.0.tgz#d9e9627bfc1ef22ada3b733c2e7558bbe78e589c" - integrity sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ== - dependencies: - callsites "^3.1.0" - debug "^4.2.0" - is-observable "^2.1.0" - observable-fns "^0.6.1" - optionalDependencies: - tiny-worker ">= 2" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -"tiny-worker@>= 2": - version "2.3.0" - resolved "https://registry.yarnpkg.com/tiny-worker/-/tiny-worker-2.3.0.tgz#715ae34304c757a9af573ae9a8e3967177e6011e" - integrity sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g== - dependencies: - esm "^3.2.25" - -tinyglobby@^0.2.14: - version "0.2.14" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" - integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== - dependencies: - fdir "^6.4.4" - picomatch "^4.0.2" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-to-zod@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/ts-to-zod/-/ts-to-zod-3.15.0.tgz#3784780f2c52e69d5c48199d3e18f83aec5c5109" - integrity sha512-Lu5ITqD8xCIo4JZp4Cg3iSK3J2x3TGwwuDtNHfAIlx1mXWKClRdzqV+x6CFEzhKtJlZzhyvJIqg7DzrWfsdVSg== - dependencies: - "@oclif/core" ">=3.26.0" - "@typescript/vfs" "^1.5.0" - case "^1.6.3" - chokidar "^3.5.1" - fs-extra "^11.1.1" - inquirer "^8.2.0" - lodash "^4.17.21" - ora "^5.4.0" - prettier "3.0.3" - rxjs "^7.4.0" - slash "^3.0.0" - threads "^1.7.0" - tslib "^2.3.1" - tsutils "^3.21.0" - typescript "^5.2.2" - zod "^3.23.8" - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.1.0, tslib@^2.3.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -typescript@^5.2.2: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -zod@^3.23.8: - version "3.25.76" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" - integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-core" +version = "0.1.0" +authors = ["Radroots Authors"] +license = "AGPLv3" +edition = "2021" + +[features] +default = ["std", "serde", "typeshare"] +std = [] +serde = ["dep:serde", "rust_decimal/serde"] +typeshare = ["dep:typeshare"] + +[dependencies] +rust_decimal = { version = "1", default-features = false } +rust_decimal_macros = "1" +serde = { version = "1", default-features = false, features = ["derive"], optional = true } +typeshare = { version = "1", optional = true } diff --git a/crates/core/src/currency.rs b/crates/core/src/currency.rs @@ -0,0 +1,120 @@ +use core::fmt; +use core::str::FromStr; + +#[cfg(feature = "serde")] +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; + +#[typeshare::typeshare] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RadrootsCoreCurrency([u8; 3]); + +impl RadrootsCoreCurrency { + #[inline] + pub const fn from_const(bytes: [u8; 3]) -> Self { + Self(bytes) + } + + #[inline] + pub fn from_str_upper(s: &str) -> Result<Self, RadrootsCoreCurrencyParseError> { + let b = s.as_bytes(); + if b.len() != 3 || b.iter().any(|c| !c.is_ascii_uppercase()) { + return Err(RadrootsCoreCurrencyParseError::InvalidFormat); + } + Ok(Self([b[0], b[1], b[2]])) + } + + #[inline] + pub fn as_str(&self) -> &str { + core::str::from_utf8(&self.0).expect("currency bytes are validated on construction") + } + + pub const USD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"USD"); + pub const EUR: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"EUR"); + pub const GBP: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"GBP"); + pub const JPY: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"JPY"); + pub const CAD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"CAD"); + pub const AUD: RadrootsCoreCurrency = RadrootsCoreCurrency(*b"AUD"); + + #[inline] + pub const fn minor_unit_exponent(&self) -> u32 { + match self.0 { + [b'J', b'P', b'Y'] | [b'K', b'R', b'W'] | [b'V', b'N', b'D'] => 0, + [b'B', b'H', b'D'] + | [b'I', b'Q', b'D'] + | [b'J', b'O', b'D'] + | [b'K', b'W', b'D'] + | [b'L', b'Y', b'D'] + | [b'O', b'M', b'R'] + | [b'T', b'N', b'D'] => 3, + _ => 2, + } + } +} + +impl fmt::Debug for RadrootsCoreCurrency { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("RadrootsCoreCurrency") + .field(&self.as_str()) + .finish() + } +} + +impl fmt::Display for RadrootsCoreCurrency { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl TryFrom<&str> for RadrootsCoreCurrency { + type Error = RadrootsCoreCurrencyParseError; + fn try_from(s: &str) -> Result<Self, Self::Error> { + s.parse() + } +} + +impl FromStr for RadrootsCoreCurrency { + type Err = RadrootsCoreCurrencyParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = s.trim(); + if s.len() != 3 || !s.chars().all(|c| c.is_ascii_alphabetic()) { + return Err(RadrootsCoreCurrencyParseError::InvalidFormat); + } + let upper = s.to_ascii_uppercase(); + Self::from_str_upper(&upper) + } +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCoreCurrencyParseError { + InvalidFormat, +} + +impl fmt::Display for RadrootsCoreCurrencyParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RadrootsCoreCurrencyParseError::InvalidFormat => { + write!(f, "currency must be a 3-letter code") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCoreCurrencyParseError {} + +#[cfg(feature = "serde")] +impl Serialize for RadrootsCoreCurrency { + fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { + ser.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for RadrootsCoreCurrency { + fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { + let s = String::deserialize(de)?; + s.parse().map_err(D::Error::custom) + } +} diff --git a/crates/core/src/decimal.rs b/crates/core/src/decimal.rs @@ -0,0 +1,149 @@ +use core::fmt; +use core::ops::{Add, Div, Mul, Sub}; +use core::str::FromStr; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; + +#[cfg(feature = "serde")] +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; + +#[typeshare::typeshare] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct RadrootsCoreDecimal(pub Decimal); + +impl RadrootsCoreDecimal { + pub const ZERO: Self = Self(Decimal::ZERO); + pub const ONE: Self = Self(Decimal::ONE); + + #[inline] + pub fn is_zero(&self) -> bool { + self.0.is_zero() + } + #[inline] + pub fn is_sign_negative(&self) -> bool { + self.0.is_sign_negative() + } + #[inline] + pub fn rescale(&mut self, scale: u32) { + self.0.rescale(scale); + } + #[inline] + pub fn normalize(&self) -> Decimal { + self.0.normalize() + } + + #[inline] + pub fn scale(&self) -> u32 { + self.0.scale() + } + + #[inline] + pub fn from_str_exact(s: &str) -> Result<Self, rust_decimal::Error> { + Decimal::from_str_exact(s).map(Self) + } + + #[inline] + pub fn from_f64_display(n: f64) -> Result<Self, rust_decimal::Error> { + let s = format!("{:.17}", n); + Decimal::from_str(&s).map(Self) + } + #[inline] + pub fn to_f64_lossy(&self) -> f64 { + self.normalize().to_string().parse::<f64>().unwrap_or(0.0) + } + + #[inline] + pub fn to_u64_exact(&self) -> Option<u64> { + if self.0.fract().is_zero() { + self.0.to_u64() + } else { + None + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for RadrootsCoreDecimal { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + serializer.serialize_str(&self.normalize().to_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for RadrootsCoreDecimal { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + let s = String::deserialize(deserializer)?; + Decimal::from_str(&s) + .map(RadrootsCoreDecimal) + .map_err(D::Error::custom) + } +} + +impl fmt::Display for RadrootsCoreDecimal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.normalize().to_string()) + } +} + +impl From<Decimal> for RadrootsCoreDecimal { + fn from(d: Decimal) -> Self { + Self(d) + } +} +impl From<RadrootsCoreDecimal> for Decimal { + fn from(d: RadrootsCoreDecimal) -> Self { + d.0 + } +} +impl From<u32> for RadrootsCoreDecimal { + fn from(v: u32) -> Self { + Self(Decimal::from(v)) + } +} +impl From<i32> for RadrootsCoreDecimal { + fn from(v: i32) -> Self { + Self(Decimal::from(v)) + } +} +impl From<u64> for RadrootsCoreDecimal { + fn from(v: u64) -> Self { + Self(Decimal::from(v)) + } +} +impl From<i64> for RadrootsCoreDecimal { + fn from(v: i64) -> Self { + Self(Decimal::from(v)) + } +} + +impl Add for RadrootsCoreDecimal { + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } +} +impl Sub for RadrootsCoreDecimal { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } +} +impl Mul for RadrootsCoreDecimal { + type Output = Self; + fn mul(self, rhs: Self) -> Self { + Self(self.0 * rhs.0) + } +} +impl Div for RadrootsCoreDecimal { + type Output = Self; + fn div(self, rhs: Self) -> Self { + Self(self.0 / rhs.0) + } +} + +impl FromStr for RadrootsCoreDecimal { + type Err = rust_decimal::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Decimal::from_str(s).map(RadrootsCoreDecimal) + } +} diff --git a/crates/core/src/discount.rs b/crates/core/src/discount.rs @@ -0,0 +1,56 @@ +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +pub enum RadrootsCoreDiscountValue { + Money(crate::RadrootsCoreMoney), + Percent(crate::RadrootsCorePercent), +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +pub enum RadrootsCoreDiscount { + QuantityThreshold { + ref_key: Option<String>, + threshold: crate::RadrootsCoreQuantity, + value: crate::RadrootsCoreMoney, + }, + MassThreshold { + threshold: crate::RadrootsCoreQuantity, + value: crate::RadrootsCoreMoney, + }, + SubtotalThreshold { + threshold: crate::RadrootsCoreMoney, + value: RadrootsCoreDiscountValue, + }, + TotalThreshold { + total_min: crate::RadrootsCoreMoney, + value: crate::RadrootsCorePercent, + }, +} + +impl RadrootsCoreDiscount { + pub fn is_non_negative(&self) -> bool { + match self { + RadrootsCoreDiscount::QuantityThreshold { + threshold, value, .. + } => !threshold.amount.is_sign_negative() && !value.amount.is_sign_negative(), + RadrootsCoreDiscount::MassThreshold { threshold, value } => { + !threshold.amount.is_sign_negative() && !value.amount.is_sign_negative() + } + RadrootsCoreDiscount::SubtotalThreshold { threshold, value } => { + let money_ok = !threshold.amount.is_sign_negative(); + let val_ok = match value { + RadrootsCoreDiscountValue::Money(m) => !m.amount.is_sign_negative(), + RadrootsCoreDiscountValue::Percent(p) => !p.value.is_sign_negative(), + }; + money_ok && val_ok + } + RadrootsCoreDiscount::TotalThreshold { total_min, value } => { + !total_min.amount.is_sign_negative() && !value.value.is_sign_negative() + } + } + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs @@ -0,0 +1,24 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(unsafe_code)] +#[cfg(not(feature = "std"))] +extern crate alloc; + +pub mod currency; +pub mod decimal; +pub mod discount; +pub mod money; +pub mod percent; +pub mod quantity; +pub mod quantity_price; +#[cfg(feature = "serde")] +pub mod serde_ext; +pub mod unit; + +pub use currency::{RadrootsCoreCurrency, RadrootsCoreCurrencyParseError}; +pub use decimal::RadrootsCoreDecimal; +pub use discount::{RadrootsCoreDiscount, RadrootsCoreDiscountValue}; +pub use money::{RadrootsCoreMoney, RadrootsCoreMoneyInvariantError}; +pub use percent::{RadrootsCorePercent, RadrootsCorePercentParseError}; +pub use quantity::{RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError}; +pub use quantity_price::{RadrootsCoreQuantityPrice, RadrootsCoreQuantityPriceOps}; +pub use unit::{RadrootsCoreUnit, RadrootsCoreUnitParseError}; diff --git a/crates/core/src/money.rs b/crates/core/src/money.rs @@ -0,0 +1,225 @@ +use core::fmt; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal::RoundingStrategy; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsCoreMoney { + pub amount: crate::RadrootsCoreDecimal, + pub currency: crate::RadrootsCoreCurrency, +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCoreMoneyInvariantError { + NegativeAmount, + NotWholeMinorUnits, + AmountOverflow, + CurrencyMismatch, +} + +impl fmt::Display for RadrootsCoreMoneyInvariantError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NegativeAmount => write!(f, "money amount must be ≥ 0"), + Self::NotWholeMinorUnits => write!(f, "money not a whole number of minor units"), + Self::AmountOverflow => write!(f, "money minor-unit conversion overflow"), + Self::CurrencyMismatch => write!(f, "money currency mismatch"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCoreMoneyInvariantError {} + +impl RadrootsCoreMoney { + #[inline] + pub fn new(amount: crate::RadrootsCoreDecimal, currency: crate::RadrootsCoreCurrency) -> Self { + Self { amount, currency } + } + + #[inline] + pub fn zero(currency: crate::RadrootsCoreCurrency) -> Self { + Self { + amount: crate::RadrootsCoreDecimal::ZERO, + currency, + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.amount.is_zero() + } + + #[inline] + pub fn ensure_non_negative(&self) -> Result<(), RadrootsCoreMoneyInvariantError> { + if self.amount.is_sign_negative() { + return Err(RadrootsCoreMoneyInvariantError::NegativeAmount); + } + Ok(()) + } + + #[inline] + pub fn quantize_to_currency(mut self) -> Self { + let e = self.currency.minor_unit_exponent(); + self.amount.0 = self + .amount + .0 + .round_dp_with_strategy(e, RoundingStrategy::MidpointAwayFromZero); + self + } + + #[inline] + pub fn with_scale(mut self, scale: u32) -> Self { + self.amount.rescale(scale); + self + } + + #[inline] + pub fn checked_add(&self, rhs: &Self) -> Result<Self, RadrootsCoreMoneyInvariantError> { + if self.currency != rhs.currency { + return Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch); + } + Ok(Self::new(self.amount + rhs.amount, self.currency)) + } + + #[inline] + pub fn checked_sub(&self, rhs: &Self) -> Result<Self, RadrootsCoreMoneyInvariantError> { + if self.currency != rhs.currency { + return Err(RadrootsCoreMoneyInvariantError::CurrencyMismatch); + } + Ok(Self::new(self.amount - rhs.amount, self.currency)) + } + + #[inline] + pub fn mul_decimal(&self, factor: crate::RadrootsCoreDecimal) -> Self { + Self::new(self.amount * factor, self.currency) + } + + #[inline] + pub fn div_decimal(&self, divisor: crate::RadrootsCoreDecimal) -> Self { + Self::new(self.amount / divisor, self.currency) + } + + #[inline] + pub fn from_minor_units_u64(amount_minor: u64, currency: crate::RadrootsCoreCurrency) -> Self { + let e = currency.minor_unit_exponent(); + let major = Decimal::from_i128_with_scale(amount_minor as i128, e); + Self::new(crate::RadrootsCoreDecimal(major), currency) + } + + #[inline] + pub fn from_minor_units_u32(amount_minor: u32, currency: crate::RadrootsCoreCurrency) -> Self { + Self::from_minor_units_u64(amount_minor as u64, currency) + } + + #[inline] + fn pow10(e: u32) -> Decimal { + match e { + 0 => Decimal::ONE, + 1 => Decimal::from(10u32), + 2 => Decimal::from(100u32), + 3 => Decimal::from(1_000u32), + _ => { + let p = 10u128.pow(e.min(38)); + Decimal::from(p) + } + } + } + + #[inline] + pub fn to_minor_units_u64_exact(&self) -> Result<u64, RadrootsCoreMoneyInvariantError> { + let e = self.currency.minor_unit_exponent(); + let scaled = self + .amount + .0 + .round_dp_with_strategy(e, RoundingStrategy::MidpointAwayFromZero); + let as_minor = scaled * Self::pow10(e); + + if !as_minor.fract().is_zero() { + return Err(RadrootsCoreMoneyInvariantError::NotWholeMinorUnits); + } + as_minor + .to_u64() + .ok_or(RadrootsCoreMoneyInvariantError::AmountOverflow) + } + + #[inline] + pub fn to_minor_units_u64_rounded( + &self, + strategy: RoundingStrategy, + ) -> Result<u64, RadrootsCoreMoneyInvariantError> { + let e = self.currency.minor_unit_exponent(); + let scaled = self.amount.0.round_dp_with_strategy(e, strategy); + let as_minor = scaled * Self::pow10(e); + if !as_minor.fract().is_zero() { + return Err(RadrootsCoreMoneyInvariantError::NotWholeMinorUnits); + } + as_minor + .to_u64() + .ok_or(RadrootsCoreMoneyInvariantError::AmountOverflow) + } + + #[inline] + pub fn to_minor_units_u32_exact(&self) -> Result<u32, RadrootsCoreMoneyInvariantError> { + let v = self.to_minor_units_u64_exact()?; + u32::try_from(v).map_err(|_| RadrootsCoreMoneyInvariantError::AmountOverflow) + } + + #[inline] + pub fn to_minor_units_u32_rounded( + &self, + strategy: RoundingStrategy, + ) -> Result<u32, RadrootsCoreMoneyInvariantError> { + let v = self.to_minor_units_u64_rounded(strategy)?; + u32::try_from(v).map_err(|_| RadrootsCoreMoneyInvariantError::AmountOverflow) + } +} + +impl fmt::Display for RadrootsCoreMoney { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.amount, self.currency) + } +} + +use core::ops::{Add, Div, Mul, Sub}; + +impl Add for RadrootsCoreMoney { + type Output = Self; + fn add(self, rhs: Self) -> Self { + assert_eq!( + self.currency, rhs.currency, + "money currency mismatch: {} vs {}", + self.currency, rhs.currency + ); + Self::new(self.amount + rhs.amount, self.currency) + } +} + +impl Sub for RadrootsCoreMoney { + type Output = Self; + fn sub(self, rhs: Self) -> Self { + assert_eq!( + self.currency, rhs.currency, + "money currency mismatch: {} vs {}", + self.currency, rhs.currency + ); + Self::new(self.amount - rhs.amount, self.currency) + } +} + +impl Mul<crate::RadrootsCoreDecimal> for RadrootsCoreMoney { + type Output = Self; + fn mul(self, rhs: crate::RadrootsCoreDecimal) -> Self { + self.mul_decimal(rhs) + } +} + +impl Div<crate::RadrootsCoreDecimal> for RadrootsCoreMoney { + type Output = Self; + fn div(self, rhs: crate::RadrootsCoreDecimal) -> Self { + self.div_decimal(rhs) + } +} diff --git a/crates/core/src/percent.rs b/crates/core/src/percent.rs @@ -0,0 +1,78 @@ +use core::fmt; +use core::str::FromStr; + +use crate::money::RadrootsCoreMoney; +use crate::RadrootsCoreDecimal; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsCorePercent { + #[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))] + pub value: RadrootsCoreDecimal, +} + +impl RadrootsCorePercent { + #[inline] + pub fn new(value: RadrootsCoreDecimal) -> Self { + Self { value } + } + + #[inline] + pub fn from_ratio(ratio_0_to_1: RadrootsCoreDecimal) -> Self { + Self { + value: ratio_0_to_1 * RadrootsCoreDecimal::from(100u32), + } + } + + #[inline] + pub fn to_ratio(&self) -> RadrootsCoreDecimal { + self.value / RadrootsCoreDecimal::from(100u32) + } + + #[inline] + pub fn of_money(&self, base: &RadrootsCoreMoney) -> RadrootsCoreMoney { + base.mul_decimal(self.to_ratio()) + } + + #[inline] + pub fn of_money_quantized(&self, base: &RadrootsCoreMoney) -> RadrootsCoreMoney { + base.mul_decimal(self.to_ratio()).quantize_to_currency() + } +} + +impl fmt::Display for RadrootsCorePercent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}%", self.value.normalize()) + } +} + +impl FromStr for RadrootsCorePercent { + type Err = RadrootsCorePercentParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let trimmed = s.trim_end(); + let no_pct = trimmed.strip_suffix('%').unwrap_or(trimmed).trim(); + let dec = no_pct + .parse::<RadrootsCoreDecimal>() + .map_err(|_| RadrootsCorePercentParseError::InvalidNumber)?; + Ok(RadrootsCorePercent::new(dec)) + } +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCorePercentParseError { + InvalidNumber, +} + +impl fmt::Display for RadrootsCorePercentParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RadrootsCorePercentParseError::InvalidNumber => write!(f, "invalid percent string"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCorePercentParseError {} diff --git a/crates/core/src/quantity.rs b/crates/core/src/quantity.rs @@ -0,0 +1,235 @@ +use core::fmt; + +use crate::unit::RadrootsCoreUnit; +use crate::RadrootsCoreDecimal; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsCoreQuantity { + #[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))] + pub amount: RadrootsCoreDecimal, + pub unit: RadrootsCoreUnit, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub label: Option<String>, +} + +impl RadrootsCoreQuantity { + #[inline] + pub fn new(amount: RadrootsCoreDecimal, unit: RadrootsCoreUnit) -> Self { + Self { + amount, + unit, + label: None, + } + } + + #[inline] + pub fn with_label<S: Into<String>>(mut self, label: S) -> Self { + self.label = Some(label.into()); + self + } + + #[inline] + pub fn with_optional_label<S: Into<String>>(mut self, label: Option<S>) -> Self { + self.label = label.map(|s| s.into()); + self + } + + #[inline] + pub fn clear_label(mut self) -> Self { + self.label = None; + self + } + + #[inline] + pub fn zero(unit: RadrootsCoreUnit) -> Self { + Self { + amount: RadrootsCoreDecimal::ZERO, + unit, + label: None, + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.amount.is_zero() + } + + #[inline] + pub fn ensure_non_negative(&self) -> Result<(), RadrootsCoreQuantityInvariantError> { + if self.amount.is_sign_negative() { + return Err(RadrootsCoreQuantityInvariantError::NegativeAmount); + } + Ok(()) + } + + #[inline] + pub fn with_scale(mut self, scale: u32) -> Self { + self.amount.rescale(scale); + self + } + + #[inline] + pub fn try_add( + &self, + rhs: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> { + if self.unit != rhs.unit { + return Err(RadrootsCoreQuantityInvariantError::UnitMismatch); + } + Ok(RadrootsCoreQuantity { + amount: self.amount + rhs.amount, + unit: self.unit, + label: self.label.clone(), + }) + } + + #[inline] + pub fn try_sub( + &self, + rhs: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> { + if self.unit != rhs.unit { + return Err(RadrootsCoreQuantityInvariantError::UnitMismatch); + } + Ok(RadrootsCoreQuantity { + amount: self.amount - rhs.amount, + unit: self.unit, + label: self.label.clone(), + }) + } + + pub fn checked_add(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> { + if self.unit == rhs.unit { + Some(RadrootsCoreQuantity { + amount: self.amount + rhs.amount, + unit: self.unit, + label: self.label.clone(), + }) + } else { + None + } + } + + pub fn checked_sub(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> { + if self.unit == rhs.unit { + Some(RadrootsCoreQuantity { + amount: self.amount - rhs.amount, + unit: self.unit, + label: self.label.clone(), + }) + } else { + None + } + } + + #[inline] + pub fn mul_decimal(&self, factor: RadrootsCoreDecimal) -> RadrootsCoreQuantity { + RadrootsCoreQuantity { + amount: self.amount * factor, + unit: self.unit, + label: self.label.clone(), + } + } + + #[inline] + pub fn div_decimal(&self, divisor: RadrootsCoreDecimal) -> RadrootsCoreQuantity { + RadrootsCoreQuantity { + amount: self.amount / divisor, + unit: self.unit, + label: self.label.clone(), + } + } +} + +impl fmt::Display for RadrootsCoreQuantity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.amount.normalize(), self.unit)?; + if let Some(label) = &self.label { + write!(f, " ({label})")?; + } + Ok(()) + } +} + +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCoreQuantityInvariantError { + NegativeAmount, + UnitMismatch, +} + +impl fmt::Display for RadrootsCoreQuantityInvariantError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RadrootsCoreQuantityInvariantError::NegativeAmount => { + write!(f, "quantity amount must be ≥ 0") + } + RadrootsCoreQuantityInvariantError::UnitMismatch => { + write!(f, "quantity unit mismatch") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCoreQuantityInvariantError {} + +use core::ops::{Add, Div, Mul, Sub}; + +impl Add for RadrootsCoreQuantity { + type Output = RadrootsCoreQuantity; + fn add(self, rhs: RadrootsCoreQuantity) -> RadrootsCoreQuantity { + assert!( + self.unit == rhs.unit, + "quantity unit mismatch: {} vs {}", + self.unit, + rhs.unit + ); + RadrootsCoreQuantity { + amount: self.amount + rhs.amount, + unit: self.unit, + label: self.label, + } + } +} + +impl Sub for RadrootsCoreQuantity { + type Output = RadrootsCoreQuantity; + fn sub(self, rhs: RadrootsCoreQuantity) -> RadrootsCoreQuantity { + assert!( + self.unit == rhs.unit, + "quantity unit mismatch: {} vs {}", + self.unit, + rhs.unit + ); + RadrootsCoreQuantity { + amount: self.amount - rhs.amount, + unit: self.unit, + label: self.label, + } + } +} + +impl Mul<RadrootsCoreDecimal> for RadrootsCoreQuantity { + type Output = RadrootsCoreQuantity; + fn mul(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity { + RadrootsCoreQuantity { + amount: self.amount * rhs, + unit: self.unit, + label: self.label, + } + } +} + +impl Div<RadrootsCoreDecimal> for RadrootsCoreQuantity { + type Output = RadrootsCoreQuantity; + fn div(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity { + RadrootsCoreQuantity { + amount: self.amount / rhs, + unit: self.unit, + label: self.label, + } + } +} diff --git a/crates/core/src/quantity_price.rs b/crates/core/src/quantity_price.rs @@ -0,0 +1,135 @@ +use crate::{RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, RadrootsCoreUnit}; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsCoreQuantityPrice { + #[cfg_attr(feature = "serde", serde(alias = "money", alias = "price"))] + pub amount: RadrootsCoreMoney, + #[cfg_attr(feature = "serde", serde(alias = "per", alias = "quantity"))] + pub quantity: RadrootsCoreQuantity, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCoreQuantityPriceError { + PerQuantityZero, + UnitMismatch { + have: RadrootsCoreUnit, + want: RadrootsCoreUnit, + }, + NonConvertibleUnits { + from: RadrootsCoreUnit, + to: RadrootsCoreUnit, + }, +} + +pub trait RadrootsCoreQuantityPriceOps { + #[must_use] + fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + + #[must_use] + fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + + #[must_use] + fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney; + + fn try_cost_for( + &self, + qty: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; + + fn try_cost_for_rounded( + &self, + qty: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError>; +} + +impl RadrootsCoreQuantityPrice { + #[inline] + pub fn new(amount: RadrootsCoreMoney, quantity: RadrootsCoreQuantity) -> Self { + Self { amount, quantity } + } + + #[inline] + pub fn try_cost_for_amount_in( + &self, + amount: RadrootsCoreDecimal, + unit: RadrootsCoreUnit, + ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + use crate::unit::convert_mass_decimal; + + let target = self.quantity.unit; + + let normalized = if unit == target { + amount + } else if unit.is_mass() && target.is_mass() { + convert_mass_decimal(amount, unit, target) + } else { + return Err(RadrootsCoreQuantityPriceError::NonConvertibleUnits { + from: unit, + to: target, + }); + }; + + let qty = RadrootsCoreQuantity::new(normalized, target); + self.try_cost_for_rounded(&qty) + } +} + +impl RadrootsCoreQuantityPriceOps for RadrootsCoreQuantityPrice { + #[inline] + fn cost_for(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + if qty.amount.is_zero() { + return RadrootsCoreMoney::zero(self.amount.currency); + } + if self.quantity.amount.is_zero() { + return RadrootsCoreMoney::zero(self.amount.currency); + } + + let ratio = qty.amount / self.quantity.amount; + self.amount.mul_decimal(ratio) + } + + #[inline] + fn cost_for_rounded(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + self.cost_for(qty).quantize_to_currency() + } + + #[inline] + fn cost_for_with_quantized_price(&self, qty: &RadrootsCoreQuantity) -> RadrootsCoreMoney { + if qty.amount.is_zero() { + return RadrootsCoreMoney::zero(self.amount.currency); + } + if self.quantity.amount.is_zero() { + return RadrootsCoreMoney::zero(self.amount.currency); + } + let unit_price_q = self.amount.clone().quantize_to_currency(); + unit_price_q.mul_decimal(qty.amount / self.quantity.amount) + } + + #[inline] + fn try_cost_for( + &self, + qty: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + if self.quantity.amount.is_zero() { + return Err(RadrootsCoreQuantityPriceError::PerQuantityZero); + } + if qty.unit != self.quantity.unit { + return Err(RadrootsCoreQuantityPriceError::UnitMismatch { + have: qty.unit, + want: self.quantity.unit, + }); + } + let ratio = qty.amount / self.quantity.amount; + Ok(self.amount.mul_decimal(ratio)) + } + + #[inline] + fn try_cost_for_rounded( + &self, + qty: &RadrootsCoreQuantity, + ) -> Result<RadrootsCoreMoney, RadrootsCoreQuantityPriceError> { + Ok(self.try_cost_for(qty)?.quantize_to_currency()) + } +} diff --git a/crates/core/src/serde_ext.rs b/crates/core/src/serde_ext.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "serde")] + +use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; + +pub mod decimal_str { + use super::*; + use crate::RadrootsCoreDecimal; + use core::str::FromStr; + + pub fn serialize<S: Serializer>( + value: &RadrootsCoreDecimal, + serializer: S, + ) -> Result<S::Ok, S::Error> { + serializer.serialize_str(&value.normalize().to_string()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result<RadrootsCoreDecimal, D::Error> { + let s = String::deserialize(deserializer)?; + RadrootsCoreDecimal::from_str(&s).map_err(D::Error::custom) + } +} diff --git a/crates/core/src/unit.rs b/crates/core/src/unit.rs @@ -0,0 +1,161 @@ +use core::fmt; +use core::str::FromStr; +use rust_decimal_macros::dec; + +#[cfg(feature = "serde")] +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::RadrootsCoreDecimal; + +#[typeshare::typeshare] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum RadrootsCoreUnit { + Each, + MassKg, + MassG, + MassOz, + MassLb, + VolumeL, + VolumeMl, +} + +impl RadrootsCoreUnit { + #[inline] + pub fn code(&self) -> &'static str { + match self { + Self::Each => "each", + Self::MassKg => "kg", + Self::MassG => "g", + Self::MassOz => "oz", + Self::MassLb => "lb", + Self::VolumeL => "l", + Self::VolumeMl => "ml", + } + } + + pub fn same_dimension(a: Self, b: Self) -> bool { + use RadrootsCoreUnit::*; + matches!( + (a, b), + (Each, Each) + | (MassKg, MassKg) + | (MassKg, MassG) + | (MassKg, MassOz) + | (MassKg, MassLb) + | (MassG, MassKg) + | (MassG, MassG) + | (MassG, MassOz) + | (MassG, MassLb) + | (MassOz, MassKg) + | (MassOz, MassG) + | (MassOz, MassOz) + | (MassOz, MassLb) + | (MassLb, MassKg) + | (MassLb, MassG) + | (MassLb, MassOz) + | (MassLb, MassLb) + | (VolumeL, VolumeL) + | (VolumeL, VolumeMl) + | (VolumeMl, VolumeL) + | (VolumeMl, VolumeMl) + ) + } + + #[inline] + pub fn is_mass(&self) -> bool { + matches!( + self, + Self::MassKg | Self::MassG | Self::MassOz | Self::MassLb + ) + } +} + +impl fmt::Display for RadrootsCoreUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.code()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RadrootsCoreUnitParseError { + UnknownUnit, + NotAMassUnit, +} + +impl fmt::Display for RadrootsCoreUnitParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnknownUnit => write!(f, "unknown unit string"), + Self::NotAMassUnit => write!(f, "unit is not a mass unit"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsCoreUnitParseError {} + +impl FromStr for RadrootsCoreUnit { + type Err = RadrootsCoreUnitParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = s.trim().to_ascii_lowercase(); + match s.as_str() { + "each" | "ea" | "count" => Ok(RadrootsCoreUnit::Each), + "kg" | "kilogram" | "kilograms" => Ok(RadrootsCoreUnit::MassKg), + "g" | "gram" | "grams" => Ok(RadrootsCoreUnit::MassG), + "oz" | "ounce" | "ounces" => Ok(RadrootsCoreUnit::MassOz), + "lb" | "pound" | "pounds" => Ok(RadrootsCoreUnit::MassLb), + "l" | "liter" | "litre" | "liters" | "litres" => Ok(RadrootsCoreUnit::VolumeL), + "ml" | "milliliter" | "millilitre" | "milliliters" | "millilitres" => { + Ok(RadrootsCoreUnit::VolumeMl) + } + _ => Err(RadrootsCoreUnitParseError::UnknownUnit), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for RadrootsCoreUnit { + fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { + ser.serialize_str(self.code()) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for RadrootsCoreUnit { + fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { + let s = String::deserialize(de)?; + s.parse().map_err(D::Error::custom) + } +} + +#[inline] +pub fn parse_mass_unit(s: &str) -> Result<RadrootsCoreUnit, RadrootsCoreUnitParseError> { + let u: RadrootsCoreUnit = RadrootsCoreUnit::from_str(s)?; + if u.is_mass() { + Ok(u) + } else { + Err(RadrootsCoreUnitParseError::NotAMassUnit) + } +} + +#[inline] +fn grams_factor_decimal(u: RadrootsCoreUnit) -> RadrootsCoreDecimal { + match u { + RadrootsCoreUnit::MassG => RadrootsCoreDecimal::ONE, + RadrootsCoreUnit::MassKg => RadrootsCoreDecimal::from(1000u32), + RadrootsCoreUnit::MassOz => RadrootsCoreDecimal(dec!(28.349523125)), + RadrootsCoreUnit::MassLb => RadrootsCoreDecimal(dec!(453.59237)), + _ => RadrootsCoreDecimal::ONE, + } +} + +#[inline] +pub fn convert_mass_decimal( + amount: RadrootsCoreDecimal, + from: RadrootsCoreUnit, + to: RadrootsCoreUnit, +) -> RadrootsCoreDecimal { + let amount_g = amount * grams_factor_decimal(from); + amount_g / grams_factor_decimal(to) +} diff --git a/crates/events-codec/Cargo.toml b/crates/events-codec/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "radroots-events-codec" +version = "0.1.0" +authors = ["Radroots Authors"] +license = "AGPLv3" +edition = "2021" + +[features] +default = ["std"] +std = [] +serde = ["dep:serde", "radroots-core/serde", "radroots-events/serde"] +serde_json = ["serde", "dep:serde_json"] + +[dependencies] +radroots-core = { path = "../core", default-features = false } +radroots-events = { version = "0.1.0", path = "../events" } +serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1", optional = true } +\ No newline at end of file diff --git a/crates/events-codec/src/job/encode.rs b/crates/events-codec/src/job/encode.rs @@ -0,0 +1,91 @@ +use core::fmt; + +#[derive(Debug, Clone)] +pub struct WireEventParts { + pub kind: u32, + pub content: String, + pub tags: Vec<Vec<String>>, +} + +#[derive(Debug)] +pub enum JobEncodeError { + MissingProvidersForEncrypted, + InvalidKind(u32), + EmptyRequiredField(&'static str), +} + +impl fmt::Display for JobEncodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JobEncodeError::MissingProvidersForEncrypted => { + write!(f, "encrypted=true requires at least one provider ('p') tag") + } + JobEncodeError::InvalidKind(k) => write!(f, "invalid job event kind: {}", k), + JobEncodeError::EmptyRequiredField(n) => write!(f, "empty required field: {}", n), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for JobEncodeError {} + +pub fn canonicalize_tags(tags: &mut Vec<Vec<String>>) { + tags.retain(|t| t.first().map(|s| !s.trim().is_empty()).unwrap_or(false)); + for t in tags.iter_mut() { + for s in t.iter_mut() { + *s = s.trim().to_string(); + } + } + tags.sort_by(|a, b| a.first().cmp(&b.first()).then_with(|| a.cmp(b))); + tags.dedup(); +} + +pub fn empty_content() -> String { + String::new() +} + +#[cfg(feature = "serde_json")] +pub fn json_content<T: serde::Serialize>(value: &T) -> Result<String, JobEncodeError> { + serde_json::to_string(value).map_err(|_| JobEncodeError::EmptyRequiredField("content-json")) +} + +#[derive(Debug, Clone)] +pub struct EventDraft { + pub kind: u32, + pub created_at: u32, + pub author: String, + pub content: String, + pub tags: Vec<Vec<String>>, +} + +pub fn to_draft(parts: WireEventParts, author: impl Into<String>, created_at: u32) -> EventDraft { + EventDraft { + kind: parts.kind, + created_at, + author: author.into(), + content: parts.content, + tags: parts.tags, + } +} + +pub fn push_status_tag(tags: &mut Vec<Vec<String>>, status: &str, extra: Option<&str>) { + let mut v = vec!["status".into(), status.into()]; + if let Some(e) = extra { + v.push(e.into()); + } + tags.push(v); +} + +pub fn push_provider_tag(tags: &mut Vec<Vec<String>>, p: &str) { + tags.push(vec!["p".into(), p.into()]); +} + +pub fn push_relay_tag(tags: &mut Vec<Vec<String>>, r: &str) { + tags.push(vec!["relays".into(), r.into()]); +} + +pub fn assert_no_inputs_when_encrypted(tags: &[Vec<String>]) -> bool { + !tags + .iter() + .any(|t| t.get(0).map(|s| s == "i").unwrap_or(false)) +} diff --git a/crates/events-codec/src/job/error.rs b/crates/events-codec/src/job/error.rs @@ -0,0 +1,42 @@ +use core::fmt; + +#[derive(Debug)] +pub enum JobParseError { + MissingTag(&'static str), + InvalidTag(&'static str), + InvalidNumber(&'static str, std::num::ParseIntError), + NonWholeSats(&'static str), + AmountOverflow(&'static str), + MissingChainTag(&'static str), +} + +impl fmt::Display for JobParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JobParseError::MissingTag(t) => write!(f, "missing tag: {}", t), + JobParseError::InvalidTag(t) => write!(f, "invalid tag structure for '{}'", t), + JobParseError::InvalidNumber(t, e) => write!(f, "invalid number in '{}': {}", t, e), + JobParseError::NonWholeSats(t) => { + write!( + f, + "amount in msats is not a whole number of sats for '{}'", + t + ) + } + JobParseError::AmountOverflow(t) => { + write!(f, "amount overflow in '{}' (does not fit u32 sat)", t) + } + JobParseError::MissingChainTag(t) => write!(f, "missing required chain tag: {}", t), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for JobParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + JobParseError::InvalidNumber(_, e) => Some(e), + _ => None, + } + } +} diff --git a/crates/events-codec/src/job/feedback/decode.rs b/crates/events-codec/src/job/feedback/decode.rs @@ -0,0 +1,128 @@ +use radroots_events::{ + job::{ + feedback::models::{ + RadrootsJobFeedback, RadrootsJobFeedbackEventIndex, RadrootsJobFeedbackEventMetadata, + }, + JobPaymentRequest, + }, + RadrootsNostrEvent, RadrootsNostrEventPtr, +}; + +use crate::job::{ + error::JobParseError, + util::{feedback_status_from_tag, parse_amount_tag_sat, parse_bool_encrypted}, +}; + +pub fn job_feedback_from_tags( + kind: u32, + tags: &[Vec<String>], + content: &str, +) -> Result<RadrootsJobFeedback, JobParseError> { + let etag = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("e")) + .or_else(|| { + tags.iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("e_ref")) + }) + .ok_or(JobParseError::MissingTag("e"))?; + let req_id = etag.get(1).ok_or(JobParseError::InvalidTag("e"))?.clone(); + let relay_hint = etag.get(2).cloned(); + + let status_tag = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("status")) + .ok_or(JobParseError::MissingTag("status"))?; + + let status = match status_tag.get(1).and_then(|s| feedback_status_from_tag(s)) { + Some(s) => s, + None => return Err(JobParseError::InvalidTag("status")), + }; + + let extra_info = status_tag.get(2).cloned(); + + let payment = parse_amount_tag_sat(tags)?.map(|(sat, bolt11)| JobPaymentRequest { + amount_sat: sat, + bolt11, + }); + + let encrypted = parse_bool_encrypted(tags); + + let customer_pubkey = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .and_then(|t| t.get(1).cloned()); + + Ok(RadrootsJobFeedback { + kind: kind as u16, + status, + extra_info, + request_event: RadrootsNostrEventPtr { + id: req_id, + relays: relay_hint, + }, + customer_pubkey, + payment, + content: if content.is_empty() { + None + } else { + Some(content.to_string()) + }, + encrypted, + }) +} + +fn is_feedback_kind(kind: u32) -> bool { + kind == 7000 +} + +pub fn metadata_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + content: String, + tags: Vec<Vec<String>>, +) -> Result<RadrootsJobFeedbackEventMetadata, JobParseError> { + if !is_feedback_kind(kind) { + return Err(JobParseError::InvalidTag("kind (expected 7000)")); + } + let job_feedback = job_feedback_from_tags(kind, &tags, &content)?; + Ok(RadrootsJobFeedbackEventMetadata { + id, + author, + published_at, + job_feedback, + }) +} + +pub fn index_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + content: String, + tags: Vec<Vec<String>>, + sig: String, +) -> Result<RadrootsJobFeedbackEventIndex, JobParseError> { + let metadata = metadata_from_event( + id.clone(), + author.clone(), + published_at, + kind, + content.clone(), + tags.clone(), + )?; + Ok(RadrootsJobFeedbackEventIndex { + event: RadrootsNostrEvent { + id, + author, + created_at: published_at, + kind, + content, + tags, + sig, + }, + metadata, + }) +} diff --git a/crates/events-codec/src/job/feedback/encode.rs b/crates/events-codec/src/job/feedback/encode.rs @@ -0,0 +1,56 @@ +use radroots_events::job::feedback::models::RadrootsJobFeedback; + +use crate::job::encode::{canonicalize_tags, JobEncodeError, WireEventParts}; +use crate::job::util::{feedback_status_tag, push_amount_tag_msat}; + +pub fn job_feedback_build_tags(fb: &RadrootsJobFeedback) -> Vec<Vec<String>> { + let mut tags: Vec<Vec<String>> = Vec::new(); + + let mut st = vec![ + "status".to_string(), + feedback_status_tag(fb.status).to_string(), + ]; + if let Some(info) = &fb.extra_info { + st.push(info.clone()); + } + tags.push(st); + + let mut e = vec!["e".to_string(), fb.request_event.id.clone()]; + if let Some(r) = &fb.request_event.relays { + e.push(r.clone()); + } + tags.push(e); + + if let Some(p) = &fb.customer_pubkey { + tags.push(vec!["p".into(), p.clone()]); + } + + if let Some(pay) = &fb.payment { + push_amount_tag_msat(&mut tags, pay.amount_sat, pay.bolt11.clone()); + } + + if fb.encrypted { + tags.push(vec!["encrypted".into()]); + } + + tags +} + +pub fn to_wire_parts( + fb: &RadrootsJobFeedback, + content: &str, +) -> Result<WireEventParts, JobEncodeError> { + let kind = fb.kind as u32; + if kind != 7000 { + return Err(JobEncodeError::InvalidKind(kind)); + } + + let mut tags = job_feedback_build_tags(fb); + canonicalize_tags(&mut tags); + + Ok(WireEventParts { + kind, + content: content.to_string(), + tags, + }) +} diff --git a/crates/events-codec/src/job/mod.rs b/crates/events-codec/src/job/mod.rs @@ -0,0 +1,20 @@ +pub mod encode; +pub mod error; +pub mod util; + +pub mod feedback { + pub mod decode; + pub mod encode; +} + +pub mod request { + pub mod decode; + pub mod encode; +} + +pub mod result { + pub mod decode; + pub mod encode; +} + +pub mod traits; diff --git a/crates/events-codec/src/job/request/decode.rs b/crates/events-codec/src/job/request/decode.rs @@ -0,0 +1,112 @@ +use radroots_events::{ + job::request::models::{ + RadrootsJobInput, RadrootsJobParam, RadrootsJobRequest, RadrootsJobRequestEventIndex, + RadrootsJobRequestEventMetadata, + }, + RadrootsNostrEvent, +}; + +use crate::job::{ + error::JobParseError, + util::{parse_bid_tag_sat, parse_bool_encrypted, parse_i_tags, parse_params}, +}; + +pub fn job_request_from_tags( + kind: u32, + tags: &[Vec<String>], +) -> Result<RadrootsJobRequest, JobParseError> { + let inputs: Vec<RadrootsJobInput> = parse_i_tags(tags); + + let output = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("output")) + .and_then(|t| t.get(1).cloned()); + + let params: Vec<RadrootsJobParam> = parse_params(tags); + + let bid_sat = parse_bid_tag_sat(tags)?; + + let relays = tags + .iter() + .filter(|t| t.get(0).map(|s| s.as_str()) == Some("relays")) + .filter_map(|t| t.get(1).cloned()) + .collect::<Vec<_>>(); + + let providers = tags + .iter() + .filter(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .filter_map(|t| t.get(1).cloned()) + .collect::<Vec<_>>(); + + let topics = tags + .iter() + .filter(|t| t.get(0).map(|s| s.as_str()) == Some("t")) + .filter_map(|t| t.get(1).cloned()) + .collect::<Vec<_>>(); + + let encrypted = parse_bool_encrypted(tags); + + if encrypted && providers.is_empty() { + return Err(JobParseError::MissingTag("p")); + } + + Ok(RadrootsJobRequest { + kind: kind as u16, + inputs, + output, + params, + bid_sat, + relays, + providers, + topics, + encrypted, + }) +} + +fn is_request_kind(kind: u32) -> bool { + (5000..=5999).contains(&kind) +} + +pub fn metadata_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + tags: Vec<Vec<String>>, +) -> Result<RadrootsJobRequestEventMetadata, JobParseError> { + if !is_request_kind(kind) { + return Err(JobParseError::InvalidTag("kind (expected 5000-5999)")); + } + let job_request = job_request_from_tags(kind, &tags)?; + Ok(RadrootsJobRequestEventMetadata { + id, + author, + published_at, + job_request, + }) +} + +pub fn index_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + content: String, + tags: Vec<Vec<String>>, + sig: String, +) -> Result<RadrootsJobRequestEventIndex, JobParseError> { + let metadata = + metadata_from_event(id.clone(), author.clone(), published_at, kind, tags.clone())?; + Ok(RadrootsJobRequestEventIndex { + event: RadrootsNostrEvent { + id, + author, + created_at: published_at, + kind, + content, + tags, + sig, + }, + metadata, + }) +} diff --git a/crates/events-codec/src/job/request/encode.rs b/crates/events-codec/src/job/request/encode.rs @@ -0,0 +1,72 @@ +use radroots_events::job::request::models::RadrootsJobRequest; + +use crate::job::encode::{canonicalize_tags, JobEncodeError, WireEventParts}; +use crate::job::util::{job_input_type_tag, push_bid_tag_msat}; + +pub fn job_request_build_tags(req: &RadrootsJobRequest) -> Vec<Vec<String>> { + let mut tags: Vec<Vec<String>> = Vec::new(); + + for i in &req.inputs { + let mut t = vec!["i".to_string(), i.data.clone()]; + t.push(job_input_type_tag(i.input_type).to_string()); + if let Some(relay) = &i.relay { + t.push(relay.clone()); + } + if let Some(marker) = &i.marker { + t.push(marker.clone()); + } + tags.push(t); + } + + if let Some(out) = &req.output { + tags.push(vec!["output".into(), out.clone()]); + } + + for p in &req.params { + tags.push(vec!["param".into(), p.key.clone(), p.value.clone()]); + } + + if let Some(bid_sat) = req.bid_sat { + push_bid_tag_msat(&mut tags, bid_sat); + } + + for r in &req.relays { + tags.push(vec!["relays".into(), r.clone()]); + } + + for p in &req.providers { + tags.push(vec!["p".into(), p.clone()]); + } + + for t in &req.topics { + tags.push(vec!["t".into(), t.clone()]); + } + + if req.encrypted { + tags.push(vec!["encrypted".into()]); + } + + tags +} + +pub fn to_wire_parts( + req: &RadrootsJobRequest, + content: &str, +) -> Result<WireEventParts, JobEncodeError> { + let kind = req.kind as u32; + if !(5000..=5999).contains(&kind) { + return Err(JobEncodeError::InvalidKind(kind)); + } + if req.encrypted && req.providers.is_empty() { + return Err(JobEncodeError::MissingProvidersForEncrypted); + } + + let mut tags = job_request_build_tags(req); + canonicalize_tags(&mut tags); + + Ok(WireEventParts { + kind, + content: content.to_string(), + tags, + }) +} diff --git a/crates/events-codec/src/job/result/decode.rs b/crates/events-codec/src/job/result/decode.rs @@ -0,0 +1,125 @@ +use radroots_events::{ + job::{ + request::models::RadrootsJobInput, + result::models::{ + RadrootsJobResult, RadrootsJobResultEventIndex, RadrootsJobResultEventMetadata, + }, + JobPaymentRequest, + }, + RadrootsNostrEvent, RadrootsNostrEventPtr, +}; + +use crate::job::{ + error::JobParseError, + util::{parse_amount_tag_sat, parse_bool_encrypted, parse_i_tags}, +}; + +pub fn job_result_from_tags( + kind: u32, + tags: &[Vec<String>], + content: &str, +) -> Result<RadrootsJobResult, JobParseError> { + let etag = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("e")) + .or_else(|| { + tags.iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("e_ref")) + }) + .ok_or(JobParseError::MissingTag("e"))?; + + let req_id = etag.get(1).ok_or(JobParseError::InvalidTag("e"))?.clone(); + let relay_hint = etag.get(2).cloned(); + + let request_json = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("request")) + .and_then(|t| t.get(1).cloned()); + + let inputs: Vec<RadrootsJobInput> = parse_i_tags(tags); + + let payment = parse_amount_tag_sat(tags)?.map(|(sat, bolt11)| JobPaymentRequest { + amount_sat: sat, + bolt11, + }); + + let encrypted = parse_bool_encrypted(tags); + + let customer_pubkey = tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("p")) + .and_then(|t| t.get(1).cloned()); + + Ok(RadrootsJobResult { + kind: kind as u16, + request_event: RadrootsNostrEventPtr { + id: req_id, + relays: relay_hint, + }, + request_json, + inputs, + customer_pubkey, + payment, + content: if content.is_empty() { + None + } else { + Some(content.to_string()) + }, + encrypted, + }) +} + +fn is_result_kind(kind: u32) -> bool { + (6000..=6999).contains(&kind) +} + +pub fn metadata_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + content: String, + tags: Vec<Vec<String>>, +) -> Result<RadrootsJobResultEventMetadata, JobParseError> { + if !is_result_kind(kind) { + return Err(JobParseError::InvalidTag("kind (expected 6000-6999)")); + } + let job_result = job_result_from_tags(kind, &tags, &content)?; + Ok(RadrootsJobResultEventMetadata { + id, + author, + published_at, + job_result, + }) +} + +pub fn index_from_event( + id: String, + author: String, + published_at: u32, + kind: u32, + content: String, + tags: Vec<Vec<String>>, + sig: String, +) -> Result<RadrootsJobResultEventIndex, JobParseError> { + let metadata = metadata_from_event( + id.clone(), + author.clone(), + published_at, + kind, + content.clone(), + tags.clone(), + )?; + Ok(RadrootsJobResultEventIndex { + event: RadrootsNostrEvent { + id, + author, + created_at: published_at, + kind, + content, + tags, + sig, + }, + metadata, + }) +} diff --git a/crates/events-codec/src/job/result/encode.rs b/crates/events-codec/src/job/result/encode.rs @@ -0,0 +1,72 @@ +use radroots_events::job::result::models::RadrootsJobResult; + +use crate::job::encode::{ + assert_no_inputs_when_encrypted, canonicalize_tags, JobEncodeError, WireEventParts, +}; +use crate::job::util::{job_input_type_tag, push_amount_tag_msat}; + +pub fn job_result_build_tags(res: &RadrootsJobResult) -> Vec<Vec<String>> { + let mut tags: Vec<Vec<String>> = Vec::new(); + + let mut e = vec!["e".to_string(), res.request_event.id.clone()]; + if let Some(r) = &res.request_event.relays { + e.push(r.clone()); + } + tags.push(e); + + if let Some(j) = &res.request_json { + tags.push(vec!["request".into(), j.clone()]); + } + + if !res.encrypted { + for i in &res.inputs { + let mut t = vec!["i".to_string(), i.data.clone()]; + t.push(job_input_type_tag(i.input_type).to_string()); + if let Some(relay) = &i.relay { + t.push(relay.clone()); + } + if let Some(marker) = &i.marker { + t.push(marker.clone()); + } + tags.push(t); + } + } + + if let Some(p) = &res.customer_pubkey { + tags.push(vec!["p".into(), p.clone()]); + } + + if let Some(pay) = &res.payment { + push_amount_tag_msat(&mut tags, pay.amount_sat, pay.bolt11.clone()); + } + + if res.encrypted { + tags.push(vec!["encrypted".into()]); + } + + tags +} + +pub fn to_wire_parts( + res: &RadrootsJobResult, + content: &str, +) -> Result<WireEventParts, JobEncodeError> { + let kind = res.kind as u32; + if !(6000..=6999).contains(&kind) { + return Err(JobEncodeError::InvalidKind(kind)); + } + + let mut tags = job_result_build_tags(res); + + if res.encrypted && !assert_no_inputs_when_encrypted(&tags) { + return Err(JobEncodeError::EmptyRequiredField("inputs-when-encrypted")); + } + + canonicalize_tags(&mut tags); + + Ok(WireEventParts { + kind, + content: content.to_string(), + tags, + }) +} diff --git a/crates/events-codec/src/job/traits.rs b/crates/events-codec/src/job/traits.rs @@ -0,0 +1,181 @@ +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use radroots_events::job::{ + feedback::models::{RadrootsJobFeedbackEventIndex, RadrootsJobFeedbackEventMetadata}, + request::models::{RadrootsJobRequestEventIndex, RadrootsJobRequestEventMetadata}, + result::models::{RadrootsJobResultEventIndex, RadrootsJobResultEventMetadata}, +}; + +use crate::job::{ + error::JobParseError, + feedback::decode::{ + index_from_event as feedback_index_from_event, + metadata_from_event as feedback_metadata_from_event, + }, + request::decode::{ + index_from_event as request_index_from_event, + metadata_from_event as request_metadata_from_event, + }, + result::decode::{ + index_from_event as result_index_from_event, + metadata_from_event as result_metadata_from_event, + }, +}; + +pub trait JobEventLike { + fn raw_id(&self) -> String; + fn raw_author(&self) -> String; + fn raw_published_at(&self) -> u32; + fn raw_kind(&self) -> u32; + fn raw_content(&self) -> String; + fn raw_tags(&self) -> Vec<Vec<String>>; + fn raw_sig(&self) -> String; + + fn to_job_request_metadata(&self) -> Result<RadrootsJobRequestEventMetadata, JobParseError> { + request_metadata_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_tags(), + ) + } + + fn to_job_request_event_index(&self) -> Result<RadrootsJobRequestEventIndex, JobParseError> { + request_index_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_content(), + self.raw_tags(), + self.raw_sig(), + ) + } + + fn to_job_result_metadata(&self) -> Result<RadrootsJobResultEventMetadata, JobParseError> { + result_metadata_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_content(), + self.raw_tags(), + ) + } + + fn to_job_result_event_index(&self) -> Result<RadrootsJobResultEventIndex, JobParseError> { + result_index_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_content(), + self.raw_tags(), + self.raw_sig(), + ) + } + + fn to_job_feedback_metadata(&self) -> Result<RadrootsJobFeedbackEventMetadata, JobParseError> { + feedback_metadata_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_content(), + self.raw_tags(), + ) + } + + fn to_job_feedback_event_index(&self) -> Result<RadrootsJobFeedbackEventIndex, JobParseError> { + feedback_index_from_event( + self.raw_id(), + self.raw_author(), + self.raw_published_at(), + self.raw_kind(), + self.raw_content(), + self.raw_tags(), + self.raw_sig(), + ) + } +} + +pub trait JobEventBorrow<'a> { + fn raw_id(&'a self) -> &'a str; + fn raw_author(&'a self) -> &'a str; + fn raw_content(&'a self) -> &'a str; + fn raw_kind(&'a self) -> u32; +} + +#[derive(Clone, Copy)] +pub struct BorrowedEventAdapter<'a, E: JobEventBorrow<'a>> { + inner: &'a E, + published_at: u32, + tags: &'a [Vec<String>], + sig: &'a str, +} + +impl<'a, E: JobEventBorrow<'a>> BorrowedEventAdapter<'a, E> { + pub fn new(inner: &'a E, published_at: u32, tags: &'a [Vec<String>], sig: &'a str) -> Self { + Self { + inner, + published_at, + tags, + sig, + } + } +} + +impl<'a, E: JobEventBorrow<'a>> JobEventLike for BorrowedEventAdapter<'a, E> { + #[inline] + fn raw_id(&self) -> String { + self.inner.raw_id().to_owned() + } + #[inline] + fn raw_author(&self) -> String { + self.inner.raw_author().to_owned() + } + #[inline] + fn raw_published_at(&self) -> u32 { + self.published_at + } + #[inline] + fn raw_kind(&self) -> u32 { + self.inner.raw_kind() + } + #[inline] + fn raw_content(&self) -> String { + self.inner.raw_content().to_owned() + } + #[inline] + fn raw_tags(&self) -> Vec<Vec<String>> { + self.tags.to_vec() + } + #[inline] + fn raw_sig(&self) -> String { + self.sig.to_owned() + } +} + +impl<'a> JobEventBorrow<'a> for radroots_events::RadrootsNostrEvent { + #[inline] + fn raw_id(&'a self) -> &'a str { + &self.id + } + #[inline] + fn raw_author(&'a self) -> &'a str { + &self.author + } + #[inline] + fn raw_content(&'a self) -> &'a str { + &self.content + } + #[inline] + fn raw_kind(&'a self) -> u32 { + self.kind + } +} diff --git a/crates/events-codec/src/job/util.rs b/crates/events-codec/src/job/util.rs @@ -0,0 +1,233 @@ +use radroots_events::job::{ + request::models::{RadrootsJobInput, RadrootsJobParam}, + JobFeedbackStatus, JobInputType, +}; + +use crate::job::error::JobParseError; + +fn looks_like_hex_id(s: &str) -> bool { + let n = s.len(); + (n == 32 || n == 64) && s.chars().all(|c| c.is_ascii_hexdigit()) +} + +fn looks_like_url_or_nostr(s: &str) -> bool { + let ls = s.to_ascii_lowercase(); + ls.starts_with("http://") + || ls.starts_with("https://") + || ls.starts_with("nostr:") + || ls.starts_with("note") + || ls.starts_with("nevent") + || ls.starts_with("naddr") + || looks_like_hex_id(s) +} + +fn looks_like_ws_relay(s: &str) -> bool { + let ls = s.to_ascii_lowercase(); + ls.starts_with("ws://") || ls.starts_with("wss://") +} + +pub fn parse_bool_encrypted(tags: &[Vec<String>]) -> bool { + tags.iter() + .any(|t| t.get(0).map(|s| s.as_str()) == Some("encrypted")) +} + +#[inline] +pub fn job_input_type_tag(t: JobInputType) -> &'static str { + match t { + JobInputType::Url => "url", + JobInputType::Event => "event", + JobInputType::Job => "job", + JobInputType::Text => "text", + } +} + +#[inline] +pub fn job_input_type_from_tag(s: &str) -> Option<JobInputType> { + match s { + "url" => Some(JobInputType::Url), + "event" => Some(JobInputType::Event), + "job" => Some(JobInputType::Job), + "text" => Some(JobInputType::Text), + _ => None, + } +} + +#[inline] +pub fn feedback_status_tag(s: JobFeedbackStatus) -> &'static str { + match s { + JobFeedbackStatus::PaymentRequired => "payment-required", + JobFeedbackStatus::Processing => "processing", + JobFeedbackStatus::Error => "error", + JobFeedbackStatus::Success => "success", + JobFeedbackStatus::Partial => "partial", + } +} + +#[inline] +pub fn feedback_status_from_tag(s: &str) -> Option<JobFeedbackStatus> { + match s { + "payment-required" => Some(JobFeedbackStatus::PaymentRequired), + "processing" => Some(JobFeedbackStatus::Processing), + "error" => Some(JobFeedbackStatus::Error), + "success" => Some(JobFeedbackStatus::Success), + "partial" => Some(JobFeedbackStatus::Partial), + _ => None, + } +} + +pub fn parse_i_tags(tags: &[Vec<String>]) -> Vec<RadrootsJobInput> { + let mut out = Vec::new(); + for t in tags + .iter() + .filter(|t| t.get(0).map(|s| s.as_str()) == Some("i")) + { + if t.len() < 2 { + continue; + } + + let mut data = String::new(); + let mut input_type = JobInputType::Text; + let mut relay: Option<String> = None; + let mut marker: Option<String> = None; + + match t.len() { + 2 => { + let v = &t[1]; + if looks_like_url_or_nostr(v) { + data = v.clone(); + let lv = v.to_ascii_lowercase(); + input_type = if lv.starts_with("http://") || lv.starts_with("https://") { + JobInputType::Url + } else { + JobInputType::Event + }; + } else { + marker = Some(v.clone()); + } + } + 3 => { + data = t[1].clone(); + let v = t[2].as_str(); + if let Some(it) = job_input_type_from_tag(v) { + input_type = it; + } else { + marker = Some(t[2].clone()); + } + } + 4 => { + data = t[1].clone(); + input_type = job_input_type_from_tag(t[2].as_str()).unwrap_or(JobInputType::Text); + let v = &t[3]; + if looks_like_ws_relay(v) { + relay = Some(v.clone()); + } else if marker.is_none() { + marker = Some(v.clone()); + } + } + _ => { + data = t[1].clone(); + input_type = job_input_type_from_tag(t[2].as_str()).unwrap_or(JobInputType::Text); + if let Some(v) = t.get(3) { + if looks_like_ws_relay(v) { + relay = Some(v.clone()); + if let Some(m) = t.get(4) { + marker = Some(m.clone()); + } + } else { + marker = Some(v.clone()); + } + } + if marker.is_none() { + if let Some(m) = t.get(4) { + marker = Some(m.clone()); + } + } + } + } + + out.push(RadrootsJobInput { + data, + input_type, + relay, + marker, + }); + } + out +} + +pub fn parse_params(tags: &[Vec<String>]) -> Vec<RadrootsJobParam> { + let mut params = Vec::new(); + for t in tags + .iter() + .filter(|t| t.get(0).map(|s| s.as_str()) == Some("param")) + { + if t.len() >= 3 { + params.push(RadrootsJobParam { + key: t[1].clone(), + value: t[2].clone(), + }); + } + } + params +} + +pub fn parse_amount_tag_sat( + tags: &[Vec<String>], +) -> Result<Option<(u32, Option<String>)>, JobParseError> { + let amt = match tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("amount")) + { + Some(a) => a, + None => return Ok(None), + }; + let msat_s = amt.get(1).ok_or(JobParseError::InvalidTag("amount"))?; + let msat_u64: u64 = msat_s + .parse() + .map_err(|e| JobParseError::InvalidNumber("amount", e))?; + if msat_u64 % 1000 != 0 { + return Err(JobParseError::NonWholeSats("amount")); + } + let sat_u64 = msat_u64 / 1000; + if sat_u64 > (u32::MAX as u64) { + return Err(JobParseError::AmountOverflow("amount")); + } + let bolt11 = amt.get(2).cloned(); + Ok(Some((sat_u64 as u32, bolt11))) +} + +pub fn push_amount_tag_msat(tags: &mut Vec<Vec<String>>, sat: u32, bolt11: Option<String>) { + let msat = (sat as u64) * 1000; + let mut v = vec!["amount".into(), msat.to_string()]; + if let Some(b) = bolt11 { + v.push(b); + } + tags.push(v); +} + +pub fn parse_bid_tag_sat(tags: &[Vec<String>]) -> Result<Option<u32>, JobParseError> { + let bid = match tags + .iter() + .find(|t| t.get(0).map(|s| s.as_str()) == Some("bid")) + { + Some(b) => b, + None => return Ok(None), + }; + let msat_s = bid.get(1).ok_or(JobParseError::InvalidTag("bid"))?; + let msat_u64: u64 = msat_s + .parse() + .map_err(|e| JobParseError::InvalidNumber("bid", e))?; + if msat_u64 % 1000 != 0 { + return Err(JobParseError::NonWholeSats("bid")); + } + let sat_u64 = msat_u64 / 1000; + if sat_u64 > (u32::MAX as u64) { + return Err(JobParseError::AmountOverflow("bid")); + } + Ok(Some(sat_u64 as u32)) +} + +pub fn push_bid_tag_msat(tags: &mut Vec<Vec<String>>, bid_sat: u32) { + let msat = (bid_sat as u64) * 1000; + tags.push(vec!["bid".into(), msat.to_string()]); +} diff --git a/crates/events-codec/src/lib.rs b/crates/events-codec/src/lib.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(not(feature = "std"))] +extern crate alloc; + +pub mod job; diff --git a/crates/events-indexed/Cargo.toml b/crates/events-indexed/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "radroots-events-indexed" +version = "0.1.0" +authors = ["Radroots Authors"] +license = "AGPLv3" +edition = "2021" + +[features] +default = ["serde", "typeshare"] +serde = ["dep:serde"] +typeshare = ["dep:typeshare"] +std = [] + +[dependencies] +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } +typeshare = { version = "1", optional = true } +\ No newline at end of file diff --git a/crates/events-indexed/src/checkpoint.rs b/crates/events-indexed/src/checkpoint.rs @@ -0,0 +1,47 @@ +#![allow(clippy::module_name_repetitions)] +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use crate::types::RadrootsEventsIndexedShardId; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsEventsIndexedShardCheckpoint { + pub shard_id: RadrootsEventsIndexedShardId, + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::serde_ext::epoch_seconds::de") + )] + pub last_created_at: u32, + pub last_event_id: Option<String>, + pub cursor: Option<String>, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsEventsIndexedIndexCheckpoint { + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "crate::serde_ext::epoch_seconds::de") + )] + pub generated_at: u32, + pub shards: Vec<RadrootsEventsIndexedShardCheckpoint>, +} + +impl RadrootsEventsIndexedIndexCheckpoint { + pub fn get( + &self, + id: &RadrootsEventsIndexedShardId, + ) -> Option<&RadrootsEventsIndexedShardCheckpoint> { + self.shards.iter().find(|s| &s.shard_id == id) + } + pub fn upsert(&mut self, cp: RadrootsEventsIndexedShardCheckpoint) { + if let Some(slot) = self.shards.iter_mut().find(|s| s.shard_id == cp.shard_id) { + *slot = cp; + } else { + self.shards.push(cp); + } + } +} diff --git a/crates/events-indexed/src/lib.rs b/crates/events-indexed/src/lib.rs @@ -0,0 +1,15 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(not(feature = "std"))] +extern crate alloc; + +pub mod checkpoint; +pub mod manifest; +pub mod serde_ext; +pub mod types; + +pub use checkpoint::{RadrootsEventsIndexedIndexCheckpoint, RadrootsEventsIndexedShardCheckpoint}; +pub use manifest::{ + validate_manifest, RadrootsEventsIndexedManifest, RadrootsEventsIndexedManifestError, + RadrootsEventsIndexedShardMetadata, +}; +pub use types::{RadrootsEventsIndexedIdRange, RadrootsEventsIndexedShardId}; diff --git a/crates/events-indexed/src/manifest.rs b/crates/events-indexed/src/manifest.rs @@ -0,0 +1,84 @@ +#![allow(clippy::module_name_repetitions)] +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; +use core::fmt; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsEventsIndexedShardMetadata { + pub file: String, + pub count: u32, + pub first_id: String, + pub last_id: String, + pub first_published_at: u32, + pub last_published_at: u32, + pub sha256: String, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsEventsIndexedManifest { + pub country: String, + pub total: u32, + pub shard_size: u32, + pub first_published_at: u32, + pub last_published_at: u32, + pub shards: Vec<RadrootsEventsIndexedShardMetadata>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum RadrootsEventsIndexedManifestError { + EmptyCountry, + EmptyShards, + EmptyFile(u32), + InvalidSha256(u32), + InconsistentTotals, +} + +impl fmt::Display for RadrootsEventsIndexedManifestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RadrootsEventsIndexedManifestError::EmptyCountry => write!(f, "country is empty"), + RadrootsEventsIndexedManifestError::EmptyShards => write!(f, "no shards in manifest"), + RadrootsEventsIndexedManifestError::EmptyFile(i) => { + write!(f, "shard {} has empty file name", i) + } + RadrootsEventsIndexedManifestError::InvalidSha256(i) => { + write!(f, "shard {} has invalid sha256", i) + } + RadrootsEventsIndexedManifestError::InconsistentTotals => { + write!(f, "total does not match sum of shard counts") + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RadrootsEventsIndexedManifestError {} + +pub fn validate_manifest( + m: &RadrootsEventsIndexedManifest, +) -> Result<(), RadrootsEventsIndexedManifestError> { + if m.country.trim().is_empty() { + return Err(RadrootsEventsIndexedManifestError::EmptyCountry); + } + if m.shards.is_empty() { + return Err(RadrootsEventsIndexedManifestError::EmptyShards); + } + let mut sum: u64 = 0; + for (i, s) in m.shards.iter().enumerate() { + if s.file.trim().is_empty() { + return Err(RadrootsEventsIndexedManifestError::EmptyFile(i as u32)); + } + if s.sha256.len() != 64 || !s.sha256.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(RadrootsEventsIndexedManifestError::InvalidSha256(i as u32)); + } + sum += s.count as u64; + } + if sum as u32 != m.total { + return Err(RadrootsEventsIndexedManifestError::InconsistentTotals); + } + Ok(()) +} diff --git a/crates/events-indexed/src/serde_ext.rs b/crates/events-indexed/src/serde_ext.rs @@ -0,0 +1,17 @@ +#[cfg(feature = "serde")] +pub mod epoch_seconds { + use serde::{de::Error as DeError, Deserialize, Deserializer}; + + pub fn de<'de, D>(de: D) -> Result<u32, D::Error> + where + D: Deserializer<'de>, + { + let v = u64::deserialize(de)?; + if v > u32::MAX as u64 { + return Err(D::Error::custom( + "timestamp must be epoch **seconds**, not ms", + )); + } + Ok(v as u32) + } +} diff --git a/crates/events-indexed/src/types.rs b/crates/events-indexed/src/types.rs @@ -0,0 +1,21 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RadrootsEventsIndexedShardId(pub String); + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RadrootsEventsIndexedIdRange { + pub start: String, + pub end: String, +} + +impl RadrootsEventsIndexedIdRange { + pub fn is_valid(&self) -> bool { + !self.start.is_empty() && !self.end.is_empty() && self.start <= self.end + } +} diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "radroots-events" +version = "0.1.0" +authors = ["Radroots Authors"] +license = "AGPLv3" +edition = "2021" + +[features] +default = ["std", "serde", "typeshare"] +std = [] +serde = ["dep:serde"] +typeshare = ["dep:typeshare"] + +[dependencies] +radroots-core = { path = "../core", default-features = false, features = ["serde"] } +serde = { version = "1", default-features = false, features = ["derive"], optional = true } +typeshare = { version = "1", optional = true } + +[dev-dependencies] +serde_json = "1" +\ No newline at end of file diff --git a/crates/events/src/comment/models.rs b/crates/events/src/comment/models.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::{RadrootsNostrEvent, RadrootsNostrEventRef}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsCommentEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsCommentEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsCommentEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub comment: RadrootsComment, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsComment { + pub root: RadrootsNostrEventRef, + pub parent: RadrootsNostrEventRef, + pub content: String, +} diff --git a/crates/events/src/follow/models.rs b/crates/events/src/follow/models.rs @@ -0,0 +1,33 @@ +use crate::RadrootsNostrEvent; +use serde::{Deserialize, Serialize}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsFollowEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsFollowEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsFollowEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub follow: RadrootsFollow, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsFollow { + pub list: Vec<RadrootsFollowProfile>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsFollowProfile { + pub published_at: u32, + pub public_key: String, + pub relay_url: Option<String>, + pub contact_name: Option<String>, +} diff --git a/crates/events/src/job/feedback/models.rs b/crates/events/src/job/feedback/models.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + job::{JobFeedbackStatus, JobPaymentRequest}, + RadrootsNostrEvent, RadrootsNostrEventPtr, +}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobFeedbackEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsJobFeedbackEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobFeedbackEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub job_feedback: RadrootsJobFeedback, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsJobFeedback { + pub kind: u16, + pub status: JobFeedbackStatus, + pub extra_info: Option<String>, + pub request_event: RadrootsNostrEventPtr, + pub customer_pubkey: Option<String>, + pub payment: Option<JobPaymentRequest>, + pub content: Option<String>, + pub encrypted: bool, +} diff --git a/crates/events/src/job/mod.rs b/crates/events/src/job/mod.rs @@ -0,0 +1,41 @@ +pub mod feedback { + pub mod models; +} + +pub mod request { + pub mod models; +} + +pub mod result { + pub mod models; +} + +use serde::{Deserialize, Serialize}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Copy)] +#[serde(rename_all = "snake_case")] +pub enum JobInputType { + Url, + Event, + Job, + Text, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Copy)] +#[serde(rename_all = "snake_case")] +pub enum JobFeedbackStatus { + PaymentRequired, + Processing, + Error, + Success, + Partial, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct JobPaymentRequest { + pub amount_sat: u32, + pub bolt11: Option<String>, +} diff --git a/crates/events/src/job/request/models.rs b/crates/events/src/job/request/models.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + + +use crate::{job::JobInputType, RadrootsNostrEvent}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobRequestEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsJobRequestEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobRequestEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub job_request: RadrootsJobRequest, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsJobInput { + pub data: String, + pub input_type: JobInputType, + pub relay: Option<String>, + pub marker: Option<String>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsJobParam { + pub key: String, + pub value: String, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsJobRequest { + pub kind: u16, + pub inputs: Vec<RadrootsJobInput>, + pub output: Option<String>, + pub params: Vec<RadrootsJobParam>, + pub bid_sat: Option<u32>, + pub relays: Vec<String>, + pub providers: Vec<String>, + pub topics: Vec<String>, + pub encrypted: bool, +} diff --git a/crates/events/src/job/result/models.rs b/crates/events/src/job/result/models.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + job::{request::models::RadrootsJobInput, JobPaymentRequest}, + RadrootsNostrEvent, RadrootsNostrEventPtr, +}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobResultEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsJobResultEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsJobResultEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub job_result: RadrootsJobResult, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsJobResult { + pub kind: u16, + pub request_event: RadrootsNostrEventPtr, + pub request_json: Option<String>, + pub inputs: Vec<RadrootsJobInput>, + pub customer_pubkey: Option<String>, + pub payment: Option<JobPaymentRequest>, + pub content: Option<String>, + pub encrypted: bool, +} diff --git a/crates/events/src/kinds.rs b/crates/events/src/kinds.rs @@ -0,0 +1,38 @@ +#[typeshare::typeshare] +pub const KIND_APPLICATION_HANDLER: u32 = 31990; + +#[typeshare::typeshare] +pub const KIND_JOB_REQUEST_MIN: u32 = 5000; +#[typeshare::typeshare] +pub const KIND_JOB_REQUEST_MAX: u32 = 5999; +#[typeshare::typeshare] +pub const KIND_JOB_RESULT_MIN: u32 = 6000; +#[typeshare::typeshare] +pub const KIND_JOB_RESULT_MAX: u32 = 6999; +#[typeshare::typeshare] +pub const KIND_JOB_FEEDBACK: u32 = 7000; + +#[inline] +pub const fn is_request_kind(kind: u32) -> bool { + kind >= KIND_JOB_REQUEST_MIN && kind <= KIND_JOB_REQUEST_MAX +} +#[inline] +pub const fn is_result_kind(kind: u32) -> bool { + kind >= KIND_JOB_RESULT_MIN && kind <= KIND_JOB_RESULT_MAX +} +#[inline] +pub const fn result_kind_for_request_kind(kind: u32) -> Option<u32> { + if is_request_kind(kind) { + Some(kind + 1000) + } else { + None + } +} +#[inline] +pub const fn request_kind_for_result_kind(kind: u32) -> Option<u32> { + if is_result_kind(kind) { + Some(kind - 1000) + } else { + None + } +} diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs @@ -0,0 +1,54 @@ +pub mod job; +pub mod kinds; +pub mod tag; + +pub mod comment { + pub mod models; +} + +pub mod follow { + pub mod models; +} + +pub mod listing { + pub mod models; +} + +pub mod profile { + pub mod models; +} + +pub mod reaction { + pub mod models; +} + +use serde::{Deserialize, Serialize}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsNostrEvent { + pub id: String, + pub author: String, + pub created_at: u32, + pub kind: u32, + pub tags: Vec<Vec<String>>, + pub content: String, + pub sig: String, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsNostrEventRef { + pub id: String, + pub author: String, + pub kind: u32, + pub d_tag: Option<String>, + pub relays: Option<Vec<String>>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct RadrootsNostrEventPtr { + pub id: String, + pub relays: Option<String>, +} diff --git a/crates/events/src/listing/models.rs b/crates/events/src/listing/models.rs @@ -0,0 +1,109 @@ +use radroots_core::{ + RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCorePercent, RadrootsCoreQuantity, + RadrootsCoreQuantityPrice, +}; +use serde::{Deserialize, Serialize}; + +use crate::RadrootsNostrEvent; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsListingEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub listing: RadrootsListing, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListing { + pub d_tag: String, + pub product: RadrootsListingProduct, + pub quantities: Vec<RadrootsListingQuantity>, + pub prices: Vec<RadrootsListingPrice>, + pub discounts: Option<Vec<RadrootsListingDiscount>>, + pub location: Option<RadrootsListingLocation>, + pub images: Option<Vec<RadrootsListingImage>>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingProduct { + pub key: String, + pub title: String, + pub category: String, + pub summary: Option<String>, + pub process: Option<String>, + pub lot: Option<String>, + pub location: Option<String>, + pub profile: Option<String>, + pub year: Option<String>, +} + +#[typeshare::typeshare] +pub type RadrootsListingPrice = RadrootsCoreQuantityPrice; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingQuantity { + pub value: RadrootsCoreQuantity, + pub label: Option<String>, + pub count: Option<u32>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +pub enum RadrootsListingDiscount { + Quantity { + ref_quantity: String, + threshold: RadrootsCoreQuantity, + value: RadrootsCoreMoney, + }, + Mass { + threshold: RadrootsCoreQuantity, + value: RadrootsCoreMoney, + }, + Subtotal { + threshold: RadrootsCoreMoney, + value: RadrootsCoreDiscountValue, + }, + Total { + total_min: RadrootsCoreMoney, + value: RadrootsCorePercent, + }, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingLocation { + pub primary: String, + pub city: Option<String>, + pub region: Option<String>, + pub country: Option<String>, + pub lat: Option<f64>, + pub lng: Option<f64>, + pub geohash: Option<String>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingImage { + pub url: String, + pub size: Option<RadrootsListingImageSize>, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsListingImageSize { + pub w: u32, + pub h: u32, +} diff --git a/crates/events/src/profile/models.rs b/crates/events/src/profile/models.rs @@ -0,0 +1,33 @@ +use crate::RadrootsNostrEvent; +use serde::{Deserialize, Serialize}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsProfileEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsProfileEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsProfileEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub profile: RadrootsProfile, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsProfile { + pub name: String, + pub display_name: Option<String>, + pub nip05: Option<String>, + pub about: Option<String>, + pub website: Option<String>, + pub picture: Option<String>, + pub banner: Option<String>, + pub lud06: Option<String>, + pub lud16: Option<String>, + pub bot: Option<String>, +} diff --git a/crates/events/src/reaction/models.rs b/crates/events/src/reaction/models.rs @@ -0,0 +1,25 @@ +use crate::{RadrootsNostrEvent, RadrootsNostrEventRef}; +use serde::{Deserialize, Serialize}; + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsReactionEventIndex { + pub event: RadrootsNostrEvent, + pub metadata: RadrootsReactionEventMetadata, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsReactionEventMetadata { + pub id: String, + pub author: String, + pub published_at: u32, + pub reaction: RadrootsReaction, +} + +#[typeshare::typeshare] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RadrootsReaction { + pub root: RadrootsNostrEventRef, + pub content: String, +} diff --git a/crates/events/src/tag.rs b/crates/events/src/tag.rs @@ -0,0 +1,3 @@ +pub const TAG_E_ROOT: &str = "e_root"; +pub const TAG_E_PREV: &str = "e_prev"; +pub const TAG_D: &str = "d"; diff --git a/crates/trade/Cargo.toml b/crates/trade/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "radroots-trade" +version = "0.1.0" +authors = ["Radroots Authors"] +license = "AGPLv3" +edition = "2021" + +[features] +default = ["std", "serde", "typeshare"] +std = [] +serde = ["dep:serde", "radroots-core/serde", "radroots-events/serde", "radroots-events-codec/serde"] +typeshare = ["dep:typeshare"] + +[dependencies] +radroots-core = { path = "../core", default-features = false } +radroots-events = { path = "../events", default-features = false } +radroots-events-codec = { path = "../events-codec", default-features = false } +serde = { version = "1", default-features = false, features = ["derive"], optional = true } +typeshare = { version = "1", optional = true } diff --git a/crates/trade/src/lib.rs b/crates/trade/src/lib.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(not(feature = "std"))] +extern crate alloc; + +pub mod listing; +pub mod prelude; diff --git a/crates/trade/src/listing/kinds.rs b/crates/trade/src/listing/kinds.rs @@ -0,0 +1,60 @@ +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_ORDER_REQ: u16 = 5301; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_ORDER_RES: u16 = 6301; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_ACCEPT_REQ: u16 = 5302; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_ACCEPT_RES: u16 = 6302; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_CONVEYANCE_REQ: u16 = 5303; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_CONVEYANCE_RES: u16 = 6303; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_INVOICE_REQ: u16 = 5304; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_INVOICE_RES: u16 = 6304; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_PAYMENT_REQ: u16 = 5305; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_PAYMENT_RES: u16 = 6305; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_FULFILL_REQ: u16 = 5306; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_FULFILL_RES: u16 = 6306; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_RECEIPT_REQ: u16 = 5307; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_RECEIPT_RES: u16 = 6307; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_CANCEL_REQ: u16 = 5309; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_CANCEL_RES: u16 = 6309; + +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_REFUND_REQ: u16 = 5310; +#[typeshare::typeshare] +pub const KIND_TRADE_LISTING_REFUND_RES: u16 = 6310; + +#[inline] +pub const fn is_trade_listing_request_kind(kind: u16) -> bool { + matches!( + kind, + KIND_TRADE_LISTING_ORDER_REQ + | KIND_TRADE_LISTING_ACCEPT_REQ + | KIND_TRADE_LISTING_CONVEYANCE_REQ + | KIND_TRADE_LISTING_INVOICE_REQ + | KIND_TRADE_LISTING_PAYMENT_REQ + | KIND_TRADE_LISTING_FULFILL_REQ + | KIND_TRADE_LISTING_RECEIPT_REQ + | KIND_TRADE_LISTING_CANCEL_REQ + | KIND_TRADE_LISTING_REFUND_REQ + ) +} diff --git a/crates/trade/src/listing/meta.rs b/crates/trade/src/listing/meta.rs @@ -0,0 +1,64 @@ +use core::fmt; +use core::str::FromStr; + +pub const MARKER_LISTING: &str = "listing"; +pub const MARKER_PAYLOAD: &str = "payload"; +pub const MARKER_PREVIOUS: &str = "previous"; + +pub const MARKER_ACCEPT_RESULT: &str = "accept_result"; +pub const MARKER_INVOICE_RESULT: &str = "invoice_result"; +pub const MARKER_FULFILLMENT_RESULT: &str = "fulfillment_result"; +pub const MARKER_PROOF: &str = "proof"; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr( + feature = "serde", + serde(rename_all = "snake_case", tag = "kind", content = "amount") +)] +pub enum TradeListingStage { + Order, + Accept, + Conveyance, + Invoice, + Payment, + Fulfillment, + Receipt, + Cancel, + Refund, +} + +impl fmt::Display for TradeListingStage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + TradeListingStage::Order => "order", + TradeListingStage::Accept => "accept", + TradeListingStage::Conveyance => "conveyance", + TradeListingStage::Invoice => "invoice", + TradeListingStage::Payment => "payment", + TradeListingStage::Fulfillment => "fulfillment", + TradeListingStage::Receipt => "receipt", + TradeListingStage::Cancel => "cancel", + TradeListingStage::Refund => "refund", + }) + } +} + +impl FromStr for TradeListingStage { + type Err = (); + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "order" => Ok(Self::Order), + "accept" => Ok(Self::Accept), + "conveyance" => Ok(Self::Conveyance), + "invoice" => Ok(Self::Invoice), + "payment" => Ok(Self::Payment), + "fulfillment" => Ok(Self::Fulfillment), + "receipt" => Ok(Self::Receipt), + "cancel" => Ok(Self::Cancel), + "refund" => Ok(Self::Refund), + _ => Err(()), + } + } +} diff --git a/crates/trade/src/listing/mod.rs b/crates/trade/src/listing/mod.rs @@ -0,0 +1,15 @@ +pub mod kinds; +pub mod meta; +pub mod model; +pub mod price_ext; +pub mod tags; + +pub mod stage { + pub mod accept; + pub mod conveyance; + pub mod fulfillment; + pub mod invoice; + pub mod order; + pub mod payment; + pub mod receipt; +} diff --git a/crates/trade/src/listing/model.rs b/crates/trade/src/listing/model.rs @@ -0,0 +1,22 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct RadrootsTradeListingSubtotal { + pub price_amount: radroots_core::RadrootsCoreMoney, + pub price_currency: radroots_core::RadrootsCoreCurrency, + pub quantity_amount: radroots_core::RadrootsCoreDecimal, + pub quantity_unit: radroots_core::RadrootsCoreUnit, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct RadrootsTradeListingTotal { + pub price_amount: radroots_core::RadrootsCoreMoney, + pub price_currency: radroots_core::RadrootsCoreCurrency, + pub quantity_amount: radroots_core::RadrootsCoreDecimal, + pub quantity_unit: radroots_core::RadrootsCoreUnit, +} diff --git a/crates/trade/src/listing/price_ext.rs b/crates/trade/src/listing/price_ext.rs @@ -0,0 +1,44 @@ +use crate::listing::model::{RadrootsTradeListingSubtotal, RadrootsTradeListingTotal}; +use radroots_core::{ + RadrootsCoreDecimal, RadrootsCoreQuantity, RadrootsCoreQuantityPrice, + RadrootsCoreQuantityPriceOps, +}; +use radroots_events::listing::models::RadrootsListingQuantity; + +pub trait AsCoreQuantityPrice { + fn as_core_qp(&self) -> RadrootsCoreQuantityPrice; +} + +pub trait ListingPricingExt { + fn subtotal_for(&self, qty: &RadrootsListingQuantity) -> RadrootsTradeListingSubtotal; + fn total_for(&self, qty: &RadrootsListingQuantity) -> RadrootsTradeListingTotal; +} + +impl ListingPricingExt for RadrootsCoreQuantityPrice { + fn subtotal_for(&self, qty: &RadrootsListingQuantity) -> RadrootsTradeListingSubtotal { + let count = qty.count.unwrap_or(1); + let effective_qty = RadrootsCoreQuantity::new( + qty.value.amount * RadrootsCoreDecimal::from(count as u32), + qty.value.unit, + ); + + let money = self.cost_for_rounded(&effective_qty); + + RadrootsTradeListingSubtotal { + price_amount: money.clone(), + price_currency: self.amount.currency, + quantity_amount: effective_qty.amount, + quantity_unit: effective_qty.unit, + } + } + + fn total_for(&self, qty: &RadrootsListingQuantity) -> RadrootsTradeListingTotal { + let sub = self.subtotal_for(qty); + RadrootsTradeListingTotal { + price_amount: sub.price_amount, + price_currency: sub.price_currency, + quantity_amount: sub.quantity_amount, + quantity_unit: sub.quantity_unit, + } + } +} diff --git a/crates/trade/src/listing/stage/accept.rs b/crates/trade/src/listing/stage/accept.rs @@ -0,0 +1,19 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingAcceptRequest { + pub order_result_event_id: String, + pub listing_event_id: String, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingAcceptResult { + pub listing_event_id: String, + pub order_result_event_id: String, + pub accepted_by: String, +} diff --git a/crates/trade/src/listing/stage/conveyance.rs b/crates/trade/src/listing/stage/conveyance.rs @@ -0,0 +1,41 @@ +#![cfg_attr(not(feature = "serde"), allow(unused_attributes))] + +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +pub enum TradeListingConveyanceMethod { + SellerDelivery { + window: Option<String>, + notes: Option<String>, + }, + BuyerPickup { + location_hint: Option<String>, + by_when: Option<String>, + }, + ThirdParty { + provider: String, + ref_id: Option<String>, + notes: Option<String>, + }, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingConveyanceRequest { + pub accept_result_event_id: String, + pub method: TradeListingConveyanceMethod, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingConveyanceResult { + pub verified: bool, + pub method: TradeListingConveyanceMethod, + pub message: Option<String>, +} diff --git a/crates/trade/src/listing/stage/fulfillment.rs b/crates/trade/src/listing/stage/fulfillment.rs @@ -0,0 +1,34 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingFulfillmentRequest { + pub payment_result_event_id: String, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +#[cfg_attr( + feature = "serde", + serde(rename_all = "snake_case", tag = "kind", content = "amount") +)] +pub enum TradeListingFulfillmentState { + Preparing, + Shipped, + ReadyForPickup, + Delivered, + Canceled, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingFulfillmentResult { + pub state: TradeListingFulfillmentState, + pub tracking: Option<String>, + pub eta: Option<String>, + pub notes: Option<String>, +} diff --git a/crates/trade/src/listing/stage/invoice.rs b/crates/trade/src/listing/stage/invoice.rs @@ -0,0 +1,19 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingInvoiceRequest { + pub accept_result_event_id: String, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingInvoiceResult { + pub total_sat: u32, + pub bolt11: Option<String>, + pub note: Option<String>, + pub expires_at: Option<u32>, +} diff --git a/crates/trade/src/listing/stage/order.rs b/crates/trade/src/listing/stage/order.rs @@ -0,0 +1,33 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; +use radroots_core::RadrootsCoreQuantityPrice; +use radroots_events::listing::models::{RadrootsListingDiscount, RadrootsListingQuantity}; + +use crate::listing::model::{RadrootsTradeListingSubtotal, RadrootsTradeListingTotal}; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingOrderRequestPayload { + pub price: RadrootsCoreQuantityPrice, + pub quantity: RadrootsListingQuantity, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingOrderRequest { + pub event: radroots_events::RadrootsNostrEventPtr, + pub payload: TradeListingOrderRequestPayload, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingOrderResult { + pub quantity: RadrootsListingQuantity, + pub price: RadrootsCoreQuantityPrice, + pub discounts: Vec<RadrootsListingDiscount>, + pub subtotal: RadrootsTradeListingSubtotal, + pub total: RadrootsTradeListingTotal, +} diff --git a/crates/trade/src/listing/stage/payment.rs b/crates/trade/src/listing/stage/payment.rs @@ -0,0 +1,31 @@ +#![cfg_attr(not(feature = "serde"), allow(unused_attributes))] + +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +#[serde(rename_all = "snake_case", tag = "kind", content = "amount")] +pub enum TradeListingPaymentProof { + ZapEvent { id: String }, + Preimage { hex: String }, + Txid { id: String }, + ExternalRef { provider: String, ref_id: String }, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingPaymentProofRequest { + pub invoice_result_event_id: String, + pub proof: TradeListingPaymentProof, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingPaymentResult { + pub verified: bool, + pub message: Option<String>, +} diff --git a/crates/trade/src/listing/stage/receipt.rs b/crates/trade/src/listing/stage/receipt.rs @@ -0,0 +1,18 @@ +#[cfg(not(feature = "std"))] +use alloc::string::String; + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingReceiptRequest { + pub fulfillment_result_event_id: String, + pub note: Option<String>, +} + +#[typeshare::typeshare] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug)] +pub struct TradeListingReceiptResult { + pub acknowledged: bool, + pub at: u32, +} diff --git a/crates/trade/src/listing/tags.rs b/crates/trade/src/listing/tags.rs @@ -0,0 +1,38 @@ +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use radroots_events::tag::{TAG_D, TAG_E_PREV, TAG_E_ROOT}; +use radroots_events_codec::job::error::JobParseError; + +#[inline] +pub fn push_trade_listing_chain_tags( + tags: &mut Vec<Vec<String>>, + e_root_id: impl Into<String>, + e_prev_id: Option<impl Into<String>>, + trade_id: Option<impl Into<String>>, +) { + tags.push(vec![TAG_E_ROOT.into(), e_root_id.into()]); + if let Some(prev) = e_prev_id { + tags.push(vec![TAG_E_PREV.into(), prev.into()]); + } + if let Some(d) = trade_id { + tags.push(vec![TAG_D.into(), d.into()]); + } +} + +#[inline] +pub fn validate_trade_listing_chain(tags: &[Vec<String>]) -> Result<(), JobParseError> { + let has_root = tags + .iter() + .any(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_E_ROOT)); + if !has_root { + return Err(JobParseError::MissingChainTag(TAG_E_ROOT)); + } + let has_d = tags + .iter() + .any(|t| t.get(0).map(|s| s.as_str()) == Some(TAG_D)); + if !has_d { + return Err(JobParseError::MissingChainTag(TAG_D)); + } + Ok(()) +} diff --git a/crates/trade/src/prelude.rs b/crates/trade/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::listing::*; diff --git a/package.json b/package.json @@ -0,0 +1,18 @@ +{ + "name": "crates-packages", + "private": true, + "scripts": { + "build": "turbo run build" + }, + "devDependencies": { + "turbo": "2.5.3", + "typescript": "^5.8.3" + }, + "workspaces": { + "packages": [ + "../../global/packages/*", + "packages/bindings/*" + ] + }, + "packageManager": "yarn@1.22.22" +} +\ No newline at end of file diff --git a/packages/bindings/core/package.json b/packages/bindings/core/package.json @@ -0,0 +1,42 @@ +{ + "name": "@radroots/core-bindings", + "version": "1.0.0", + "private": true, + "license": "AGPLv3", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "prebuild": "npm run clean", + "clean": "rimraf dist", + "dev": "npm run watch", + "watch": "tsc -w" + }, + "devDependencies": { + "@radroots/dev": "*", + "@radroots/tsconfig": "*", + "rimraf": "^6.0.1", + "ts-to-zod": "^3.15.0" + }, + "dependencies": { + "zod": "^4.0.5" + }, + "publishConfig": { + "access": "public" + } +} +\ No newline at end of file diff --git a/packages/bindings/core/src/index.ts b/packages/bindings/core/src/index.ts @@ -0,0 +1 @@ +export * from "./types.js" diff --git a/packages/bindings/core/src/types.ts b/packages/bindings/core/src/types.ts @@ -0,0 +1,61 @@ +/* + Generated by typeshare 1.13.3 +*/ + +export type RadrootsCoreCurrency = [number, number, number]; + +export type RadrootsCoreDecimal = number; + +export interface RadrootsCoreMoney { + amount: RadrootsCoreDecimal; + currency: RadrootsCoreCurrency; +} + +export interface RadrootsCorePercent { + value: RadrootsCoreDecimal; +} + +export enum RadrootsCoreUnit { + Each = "Each", + MassKg = "MassKg", + MassG = "MassG", + MassOz = "MassOz", + MassLb = "MassLb", + VolumeL = "VolumeL", + VolumeMl = "VolumeMl", +} + +export interface RadrootsCoreQuantity { + amount: RadrootsCoreDecimal; + unit: RadrootsCoreUnit; + label?: string; +} + +export interface RadrootsCoreQuantityPrice { + amount: RadrootsCoreMoney; + quantity: RadrootsCoreQuantity; +} + +export type RadrootsCoreDiscount = + | { kind: "quantity_threshold", amount: { + ref_key?: string; + threshold: RadrootsCoreQuantity; + value: RadrootsCoreMoney; +}} + | { kind: "mass_threshold", amount: { + threshold: RadrootsCoreQuantity; + value: RadrootsCoreMoney; +}} + | { kind: "subtotal_threshold", amount: { + threshold: RadrootsCoreMoney; + value: RadrootsCoreDiscountValue; +}} + | { kind: "total_threshold", amount: { + total_min: RadrootsCoreMoney; + value: RadrootsCorePercent; +}}; + +export type RadrootsCoreDiscountValue = + | { kind: "money", amount: RadrootsCoreMoney } + | { kind: "percent", amount: RadrootsCorePercent }; + diff --git a/bindings/ts/tsconfig.cjs.json b/packages/bindings/core/tsconfig.cjs.json diff --git a/bindings/ts/tsconfig.esm.json b/packages/bindings/core/tsconfig.esm.json diff --git a/bindings/ts/tsconfig.json b/packages/bindings/core/tsconfig.json diff --git a/packages/bindings/events-indexed/package.json b/packages/bindings/events-indexed/package.json @@ -0,0 +1,42 @@ +{ + "name": "@radroots/events-indexed-bindings", + "version": "1.0.0", + "private": true, + "license": "AGPLv3", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "prebuild": "npm run clean", + "clean": "rimraf dist", + "dev": "npm run watch", + "watch": "tsc -w" + }, + "devDependencies": { + "@radroots/dev": "*", + "@radroots/tsconfig": "*", + "rimraf": "^6.0.1", + "ts-to-zod": "^3.15.0" + }, + "dependencies": { + "zod": "^4.0.5" + }, + "publishConfig": { + "access": "public" + } +} +\ No newline at end of file diff --git a/packages/bindings/events-indexed/src/index.ts b/packages/bindings/events-indexed/src/index.ts @@ -0,0 +1 @@ +export * from "./types.js" diff --git a/packages/bindings/events-indexed/src/types.ts b/packages/bindings/events-indexed/src/types.ts @@ -0,0 +1,42 @@ +/* + Generated by typeshare 1.13.3 +*/ + +export type RadrootsEventsIndexedShardId = string; + +export interface RadrootsEventsIndexedIdRange { + start: string; + end: string; +} + +export interface RadrootsEventsIndexedShardCheckpoint { + shard_id: RadrootsEventsIndexedShardId; + last_created_at: number; + last_event_id?: string; + cursor?: string; +} + +export interface RadrootsEventsIndexedIndexCheckpoint { + generated_at: number; + shards: RadrootsEventsIndexedShardCheckpoint[]; +} + +export interface RadrootsEventsIndexedShardMetadata { + file: string; + count: number; + first_id: string; + last_id: string; + first_published_at: number; + last_published_at: number; + sha256: string; +} + +export interface RadrootsEventsIndexedManifest { + country: string; + total: number; + shard_size: number; + first_published_at: number; + last_published_at: number; + shards: RadrootsEventsIndexedShardMetadata[]; +} + diff --git a/bindings/ts/tsconfig.cjs.json b/packages/bindings/events-indexed/tsconfig.cjs.json diff --git a/bindings/ts/tsconfig.esm.json b/packages/bindings/events-indexed/tsconfig.esm.json diff --git a/bindings/ts/tsconfig.json b/packages/bindings/events-indexed/tsconfig.json diff --git a/packages/bindings/events/package.json b/packages/bindings/events/package.json @@ -0,0 +1,43 @@ +{ + "name": "@radroots/events-bindings", + "version": "1.0.0", + "private": true, + "license": "AGPLv3", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "prebuild": "npm run clean", + "clean": "rimraf dist", + "dev": "npm run watch", + "watch": "tsc -w" + }, + "devDependencies": { + "@radroots/dev": "*", + "@radroots/tsconfig": "*", + "rimraf": "^6.0.1", + "ts-to-zod": "^3.15.0" + }, + "dependencies": { + "@radroots/core-bindings": "*", + "zod": "^4.0.5" + }, + "publishConfig": { + "access": "public" + } +} +\ No newline at end of file diff --git a/packages/bindings/events/src/index.ts b/packages/bindings/events/src/index.ts @@ -0,0 +1,3 @@ +export * from "./lib.js" +export * from "./schemas.js" +export * from "./types.js" diff --git a/packages/bindings/events/src/lib.ts b/packages/bindings/events/src/lib.ts @@ -0,0 +1,3 @@ +export const TAG_E_ROOT = "e_root"; +export const TAG_E_PREV = "e_prev"; +export const TAG_D = "d"; +\ No newline at end of file diff --git a/packages/bindings/events/src/schemas.ts b/packages/bindings/events/src/schemas.ts @@ -0,0 +1,119 @@ +import { z } from "zod"; + +export const radroots_listing_image_schema = z.object({ + url: z.string(), + size: z.object({ + w: z.number(), + h: z.number() + }).optional() +}); + +export const radroots_listing_location_schema = z.object({ + primary: z.string(), + city: z.string().optional(), + region: z.string().optional(), + country: z.string().optional(), + lat: z.number().optional(), + lng: z.number().optional(), + geohash: z.string().optional() +}); + +export const radroots_listing_discount_schema = z.union([ + z.object({ + kind: z.literal("quantity"), + amount: z.object({ + ref_quantity: z.string(), + threshold: z.any(), + value: z.any() + }) + }), + z.object({ + kind: z.literal("mass"), + amount: z.object({ + threshold: z.any(), + value: z.any() + }) + }), + z.object({ + kind: z.literal("subtotal"), + amount: z.object({ + threshold: z.any(), + value: z.any() + }) + }), + z.object({ + kind: z.literal("total"), + amount: z.object({ + total_min: z.any(), + value: z.any() + }) + }) +]); + +export const radroots_listing_price_schema = z.object({ + amount: z.any(), + quantity: z.any() +}); + +export const radroots_listing_quantity_schema = z.object({ + value: z.any(), + label: z.string().optional(), + count: z.number().optional() +}); + +export const radroots_listing_product_schema = z.object({ + key: z.string(), + title: z.string(), + category: z.string(), + summary: z.string().optional(), + process: z.string().optional(), + lot: z.string().optional(), + location: z.string().optional(), + profile: z.string().optional(), + year: z.string().optional() +}); + +export const radroots_listing_schema = z.object({ + d_tag: z.string(), + product: radroots_listing_product_schema, + quantities: z.array(radroots_listing_quantity_schema), + prices: z.array(radroots_listing_price_schema), + discounts: z.array(radroots_listing_discount_schema).optional(), + location: radroots_listing_location_schema.optional(), + images: z.array(radroots_listing_image_schema).optional() +}); + +export const radroots_profile_schema = z.object({ + name: z.string(), + display_name: z.string().optional(), + nip05: z.string().optional(), + about: z.string().optional(), + website: z.string().optional(), + picture: z.string().optional(), + banner: z.string().optional(), + lud06: z.string().optional(), + lud16: z.string().optional(), + bot: z.string().optional() +}); + +export const radroots_comment_schema = z.object({ + root: z.any(), + parent: z.any(), + content: z.string() +}); + +export const radroots_reaction_schema = z.object({ + root: z.any(), + content: z.string() +}); + +export const radroots_follow_profile_schema = z.object({ + published_at: z.number(), + public_key: z.string(), + relay_url: z.string().optional(), + contact_name: z.string().optional() +}); + +export const radroots_follow_schema = z.object({ + list: z.array(radroots_follow_profile_schema) +}); diff --git a/packages/bindings/events/src/types.ts b/packages/bindings/events/src/types.ts @@ -0,0 +1,301 @@ +import { RadrootsCoreDiscountValue, RadrootsCoreMoney, RadrootsCorePercent, RadrootsCoreQuantity, RadrootsCoreQuantityPrice } from "@radroots/core-bindings"; + +/* + Generated by typeshare 1.13.3 +*/ + +export type RadrootsListingPrice = RadrootsCoreQuantityPrice; + +export interface JobPaymentRequest { + amount_sat: number; + bolt11?: string; +} + +export interface RadrootsNostrEventRef { + id: string; + author: string; + kind: number; + d_tag?: string; + relays?: string[]; +} + +export interface RadrootsComment { + root: RadrootsNostrEventRef; + parent: RadrootsNostrEventRef; + content: string; +} + +export interface RadrootsNostrEvent { + id: string; + author: string; + created_at: number; + kind: number; + tags: string[][]; + content: string; + sig: string; +} + +export interface RadrootsCommentEventMetadata { + id: string; + author: string; + published_at: number; + comment: RadrootsComment; +} + +export interface RadrootsCommentEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsCommentEventMetadata; +} + +export interface RadrootsFollowProfile { + published_at: number; + public_key: string; + relay_url?: string; + contact_name?: string; +} + +export interface RadrootsFollow { + list: RadrootsFollowProfile[]; +} + +export interface RadrootsFollowEventMetadata { + id: string; + author: string; + published_at: number; + follow: RadrootsFollow; +} + +export interface RadrootsFollowEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsFollowEventMetadata; +} + +export enum JobFeedbackStatus { + PaymentRequired = "payment_required", + Processing = "processing", + Error = "error", + Success = "success", + Partial = "partial", +} + +export interface RadrootsNostrEventPtr { + id: string; + relays?: string; +} + +export interface RadrootsJobFeedback { + kind: number; + status: JobFeedbackStatus; + extra_info?: string; + request_event: RadrootsNostrEventPtr; + customer_pubkey?: string; + payment?: JobPaymentRequest; + content?: string; + encrypted: boolean; +} + +export interface RadrootsJobFeedbackEventMetadata { + id: string; + author: string; + published_at: number; + job_feedback: RadrootsJobFeedback; +} + +export interface RadrootsJobFeedbackEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsJobFeedbackEventMetadata; +} + +export enum JobInputType { + Url = "url", + Event = "event", + Job = "job", + Text = "text", +} + +export interface RadrootsJobInput { + data: string; + input_type: JobInputType; + relay?: string; + marker?: string; +} + +export interface RadrootsJobParam { + key: string; + value: string; +} + +export interface RadrootsJobRequest { + kind: number; + inputs: RadrootsJobInput[]; + output?: string; + params: RadrootsJobParam[]; + bid_sat?: number; + relays: string[]; + providers: string[]; + topics: string[]; + encrypted: boolean; +} + +export interface RadrootsJobRequestEventMetadata { + id: string; + author: string; + published_at: number; + job_request: RadrootsJobRequest; +} + +export interface RadrootsJobRequestEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsJobRequestEventMetadata; +} + +export interface RadrootsJobResult { + kind: number; + request_event: RadrootsNostrEventPtr; + request_json?: string; + inputs: RadrootsJobInput[]; + customer_pubkey?: string; + payment?: JobPaymentRequest; + content?: string; + encrypted: boolean; +} + +export interface RadrootsJobResultEventMetadata { + id: string; + author: string; + published_at: number; + job_result: RadrootsJobResult; +} + +export interface RadrootsJobResultEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsJobResultEventMetadata; +} + +export interface RadrootsListingProduct { + key: string; + title: string; + category: string; + summary?: string; + process?: string; + lot?: string; + location?: string; + profile?: string; + year?: string; +} + +export interface RadrootsListingQuantity { + value: RadrootsCoreQuantity; + label?: string; + count?: number; +} + +export type RadrootsListingDiscount = + | { kind: "quantity", amount: { + ref_quantity: string; + threshold: RadrootsCoreQuantity; + value: RadrootsCoreMoney; +}} + | { kind: "mass", amount: { + threshold: RadrootsCoreQuantity; + value: RadrootsCoreMoney; +}} + | { kind: "subtotal", amount: { + threshold: RadrootsCoreMoney; + value: RadrootsCoreDiscountValue; +}} + | { kind: "total", amount: { + total_min: RadrootsCoreMoney; + value: RadrootsCorePercent; +}}; + +export interface RadrootsListingLocation { + primary: string; + city?: string; + region?: string; + country?: string; + lat?: number; + lng?: number; + geohash?: string; +} + +export interface RadrootsListingImageSize { + w: number; + h: number; +} + +export interface RadrootsListingImage { + url: string; + size?: RadrootsListingImageSize; +} + +export interface RadrootsListing { + d_tag: string; + product: RadrootsListingProduct; + quantities: RadrootsListingQuantity[]; + prices: RadrootsListingPrice[]; + discounts?: RadrootsListingDiscount[]; + location?: RadrootsListingLocation; + images?: RadrootsListingImage[]; +} + +export interface RadrootsListingEventMetadata { + id: string; + author: string; + published_at: number; + listing: RadrootsListing; +} + +export interface RadrootsListingEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsListingEventMetadata; +} + +export interface RadrootsProfile { + name: string; + display_name?: string; + nip05?: string; + about?: string; + website?: string; + picture?: string; + banner?: string; + lud06?: string; + lud16?: string; + bot?: string; +} + +export interface RadrootsProfileEventMetadata { + id: string; + author: string; + published_at: number; + profile: RadrootsProfile; +} + +export interface RadrootsProfileEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsProfileEventMetadata; +} + +export interface RadrootsReaction { + root: RadrootsNostrEventRef; + content: string; +} + +export interface RadrootsReactionEventMetadata { + id: string; + author: string; + published_at: number; + reaction: RadrootsReaction; +} + +export interface RadrootsReactionEventIndex { + event: RadrootsNostrEvent; + metadata: RadrootsReactionEventMetadata; +} + +export const KIND_APPLICATION_HANDLER: number = 31990; +export const KIND_JOB_REQUEST_MIN: number = 5000; +export const KIND_JOB_REQUEST_MAX: number = 5999; +export const KIND_JOB_RESULT_MIN: number = 6000; +export const KIND_JOB_RESULT_MAX: number = 6999; +export const KIND_JOB_FEEDBACK: number = 7000; diff --git a/bindings/ts/tsconfig.cjs.json b/packages/bindings/events/tsconfig.cjs.json diff --git a/bindings/ts/tsconfig.esm.json b/packages/bindings/events/tsconfig.esm.json diff --git a/bindings/ts/tsconfig.json b/packages/bindings/events/tsconfig.json diff --git a/packages/bindings/trade/package.json b/packages/bindings/trade/package.json @@ -0,0 +1,44 @@ +{ + "name": "@radroots/trade-bindings", + "version": "1.0.0", + "private": true, + "license": "AGPLv3", + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build:esm": "tsc -p tsconfig.esm.json", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build": "npm run clean && npm run build:esm && npm run build:cjs", + "prebuild": "npm run clean", + "clean": "rimraf dist", + "dev": "npm run watch", + "watch": "tsc -w" + }, + "devDependencies": { + "@radroots/dev": "*", + "@radroots/tsconfig": "*", + "rimraf": "^6.0.1", + "ts-to-zod": "^3.15.0" + }, + "dependencies": { + "@radroots/core-bindings": "*", + "@radroots/events-bindings": "*", + "zod": "^4.0.5" + }, + "publishConfig": { + "access": "public" + } +} +\ No newline at end of file diff --git a/packages/bindings/trade/src/index.ts b/packages/bindings/trade/src/index.ts @@ -0,0 +1,2 @@ +export * from "./lib.js" +export * from "./types.js" diff --git a/packages/bindings/trade/src/lib.ts b/packages/bindings/trade/src/lib.ts @@ -0,0 +1,7 @@ +export const MARKER_LISTING = "listing"; +export const MARKER_PAYLOAD = "payload"; +export const MARKER_PREVIOUS = "previous"; +export const MARKER_ACCEPT_RESULT = "accept_result"; +export const MARKER_INVOICE_RESULT = "invoice_result"; +export const MARKER_FULFILLMENT_RESULT = "fulfillment_result"; +export const MARKER_PROOF = "proof"; +\ No newline at end of file diff --git a/packages/bindings/trade/src/types.ts b/packages/bindings/trade/src/types.ts @@ -0,0 +1,171 @@ +import { RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantityPrice, RadrootsCoreUnit } from "@radroots/core-bindings"; +import { RadrootsListingDiscount, RadrootsListingQuantity, RadrootsNostrEventPtr } from "@radroots/events-bindings"; + +/* + Generated by typeshare 1.13.3 +*/ + +export interface RadrootsTradeListingSubtotal { + price_amount: RadrootsCoreMoney; + price_currency: RadrootsCoreCurrency; + quantity_amount: RadrootsCoreDecimal; + quantity_unit: RadrootsCoreUnit; +} + +export interface RadrootsTradeListingTotal { + price_amount: RadrootsCoreMoney; + price_currency: RadrootsCoreCurrency; + quantity_amount: RadrootsCoreDecimal; + quantity_unit: RadrootsCoreUnit; +} + +export interface TradeListingAcceptRequest { + order_result_event_id: string; + listing_event_id: string; +} + +export interface TradeListingAcceptResult { + listing_event_id: string; + order_result_event_id: string; + accepted_by: string; +} + +export type TradeListingConveyanceMethod = + | { kind: "seller_delivery", amount: { + window?: string; + notes?: string; +}} + | { kind: "buyer_pickup", amount: { + location_hint?: string; + by_when?: string; +}} + | { kind: "third_party", amount: { + provider: string; + ref_id?: string; + notes?: string; +}}; + +export interface TradeListingConveyanceRequest { + accept_result_event_id: string; + method: TradeListingConveyanceMethod; +} + +export interface TradeListingConveyanceResult { + verified: boolean; + method: TradeListingConveyanceMethod; + message?: string; +} + +export interface TradeListingFulfillmentRequest { + payment_result_event_id: string; +} + +export enum TradeListingFulfillmentState { + Preparing = "Preparing", + Shipped = "Shipped", + ReadyForPickup = "ReadyForPickup", + Delivered = "Delivered", + Canceled = "Canceled", +} + +export interface TradeListingFulfillmentResult { + state: TradeListingFulfillmentState; + tracking?: string; + eta?: string; + notes?: string; +} + +export interface TradeListingInvoiceRequest { + accept_result_event_id: string; +} + +export interface TradeListingInvoiceResult { + total_sat: number; + bolt11?: string; + note?: string; + expires_at?: number; +} + +export interface TradeListingOrderRequestPayload { + price: RadrootsCoreQuantityPrice; + quantity: RadrootsListingQuantity; +} + +export interface TradeListingOrderRequest { + event: RadrootsNostrEventPtr; + payload: TradeListingOrderRequestPayload; +} + +export interface TradeListingOrderResult { + quantity: RadrootsListingQuantity; + price: RadrootsCoreQuantityPrice; + discounts: RadrootsListingDiscount[]; + subtotal: RadrootsTradeListingSubtotal; + total: RadrootsTradeListingTotal; +} + +export type TradeListingPaymentProof = + | { kind: "zap_event", amount: { + id: string; +}} + | { kind: "preimage", amount: { + hex: string; +}} + | { kind: "txid", amount: { + id: string; +}} + | { kind: "external_ref", amount: { + provider: string; + ref_id: string; +}}; + +export interface TradeListingPaymentProofRequest { + invoice_result_event_id: string; + proof: TradeListingPaymentProof; +} + +export interface TradeListingPaymentResult { + verified: boolean; + message?: string; +} + +export interface TradeListingReceiptRequest { + fulfillment_result_event_id: string; + note?: string; +} + +export interface TradeListingReceiptResult { + acknowledged: boolean; + at: number; +} + +export enum TradeListingStage { + Order = "Order", + Accept = "Accept", + Conveyance = "Conveyance", + Invoice = "Invoice", + Payment = "Payment", + Fulfillment = "Fulfillment", + Receipt = "Receipt", + Cancel = "Cancel", + Refund = "Refund", +} + +export const KIND_TRADE_LISTING_ORDER_REQ: number = 5301; +export const KIND_TRADE_LISTING_ORDER_RES: number = 6301; +export const KIND_TRADE_LISTING_ACCEPT_REQ: number = 5302; +export const KIND_TRADE_LISTING_ACCEPT_RES: number = 6302; +export const KIND_TRADE_LISTING_CONVEYANCE_REQ: number = 5303; +export const KIND_TRADE_LISTING_CONVEYANCE_RES: number = 6303; +export const KIND_TRADE_LISTING_INVOICE_REQ: number = 5304; +export const KIND_TRADE_LISTING_INVOICE_RES: number = 6304; +export const KIND_TRADE_LISTING_PAYMENT_REQ: number = 5305; +export const KIND_TRADE_LISTING_PAYMENT_RES: number = 6305; +export const KIND_TRADE_LISTING_FULFILL_REQ: number = 5306; +export const KIND_TRADE_LISTING_FULFILL_RES: number = 6306; +export const KIND_TRADE_LISTING_RECEIPT_REQ: number = 5307; +export const KIND_TRADE_LISTING_RECEIPT_RES: number = 6307; +export const KIND_TRADE_LISTING_CANCEL_REQ: number = 5309; +export const KIND_TRADE_LISTING_CANCEL_RES: number = 6309; +export const KIND_TRADE_LISTING_REFUND_REQ: number = 5310; +export const KIND_TRADE_LISTING_REFUND_RES: number = 6310; diff --git a/bindings/ts/tsconfig.cjs.json b/packages/bindings/trade/tsconfig.cjs.json diff --git a/bindings/ts/tsconfig.esm.json b/packages/bindings/trade/tsconfig.esm.json diff --git a/bindings/ts/tsconfig.json b/packages/bindings/trade/tsconfig.json diff --git a/prompt.txt b/prompt.txt @@ -1,115 +0,0 @@ - -########## file ########## -src/events/listing/models.rs -########## code ########## -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::RadrootsNostrEvent; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsListingEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub listing: RadrootsListing, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListing { - pub d_tag: String, - pub product: RadrootsListingProduct, - pub quantities: Vec<RadrootsListingQuantity>, - pub prices: Vec<RadrootsListingPrice>, - pub discounts: Option<Vec<RadrootsListingDiscount>>, - pub location: Option<RadrootsListingLocation>, - pub images: Option<Vec<RadrootsListingImage>>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingProduct { - pub key: String, - pub title: String, - pub category: String, - pub summary: Option<String>, - pub process: Option<String>, - pub lot: Option<String>, - pub location: Option<String>, - pub profile: Option<String>, - pub year: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingQuantity { - pub amt: String, - pub unit: String, - pub label: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingPrice { - pub amt: String, - pub currency: String, - pub qty_amt: String, - pub qty_unit: String, - pub qty_key: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum RadrootsListingDiscount { - Quantity { - ref_quantity: String, - threshold: String, - value: String, - currency: String, - }, - Mass { - unit: String, - threshold: String, - threshold_unit: String, - value: String, - currency: String, - }, - Subtotal { - threshold: String, - currency: String, - value: String, - measure: String, - }, - Total { - total_min: String, - value: String, - measure: String, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingLocation { - pub primary: String, - pub city: Option<String>, - pub region: Option<String>, - pub country: Option<String>, - pub lat: Option<f64>, - pub lng: Option<f64>, - pub geohash: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingImage { - pub url: String, - pub size: Option<RadrootsListingImageSize>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingImageSize { - pub w: u32, - pub h: u32, -} - diff --git a/src/events/comment/models.rs b/src/events/comment/models.rs @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::{lib::RadrootsNostrEventRef, RadrootsNostrEvent}; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsCommentEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsCommentEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsCommentEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub comment: RadrootsComment, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsComment { - pub root: RadrootsNostrEventRef, - pub parent: RadrootsNostrEventRef, - pub content: String, -} diff --git a/src/events/follow/models.rs b/src/events/follow/models.rs @@ -1,32 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::RadrootsNostrEvent; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsFollowEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsFollowEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsFollowEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub follow: RadrootsFollow, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsFollow { - pub list: Vec<RadrootsFollowProfile>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsFollowProfile { - pub published_at: u32, - pub public_key: String, - pub relay_url: Option<String>, - pub contact_name: Option<String>, -} diff --git a/src/events/lib.rs b/src/events/lib.rs @@ -1,10 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsNostrEventRef { - pub id: String, - pub author: String, - pub kind: u32, - pub d_tag: Option<String>, - pub relays: Option<Vec<String>>, -} diff --git a/src/events/listing/models.rs b/src/events/listing/models.rs @@ -1,110 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::RadrootsNostrEvent; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsListingEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub listing: RadrootsListing, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListing { - pub d_tag: String, - pub product: RadrootsListingProduct, - pub quantities: Vec<RadrootsListingQuantity>, - pub prices: Vec<RadrootsListingPrice>, - pub discounts: Option<Vec<RadrootsListingDiscount>>, - pub location: Option<RadrootsListingLocation>, - pub images: Option<Vec<RadrootsListingImage>>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingProduct { - pub key: String, - pub title: String, - pub category: String, - pub summary: Option<String>, - pub process: Option<String>, - pub lot: Option<String>, - pub location: Option<String>, - pub profile: Option<String>, - pub year: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingQuantity { - pub amt: String, - pub unit: String, - pub label: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingPrice { - pub amt: String, - pub currency: String, - pub qty_amt: String, - pub qty_unit: String, - pub qty_key: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum RadrootsListingDiscount { - Quantity { - ref_quantity: String, - threshold: String, - value: String, - currency: String, - }, - Mass { - unit: String, - threshold: String, - threshold_unit: String, - value: String, - currency: String, - }, - Subtotal { - threshold: String, - currency: String, - value: String, - measure: String, - }, - Total { - total_min: String, - value: String, - measure: String, - }, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingLocation { - pub primary: String, - pub city: Option<String>, - pub region: Option<String>, - pub country: Option<String>, - pub lat: Option<f64>, - pub lng: Option<f64>, - pub geohash: Option<String>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingImage { - pub url: String, - pub size: Option<RadrootsListingImageSize>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsListingImageSize { - pub w: u32, - pub h: u32, -} diff --git a/src/events/mod.rs b/src/events/mod.rs @@ -1,32 +0,0 @@ -pub mod lib; - -pub mod comment { - pub mod models; -} - -pub mod listing { - pub mod models; -} - -pub mod profile { - pub mod models; -} - -pub mod reaction { - pub mod models; -} - -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsNostrEvent { - pub id: String, - pub author: String, - pub created_at: u32, - pub kind: u32, - pub tags: Vec<Vec<String>>, - pub content: String, - pub sig: String, -} diff --git a/src/events/profile/models.rs b/src/events/profile/models.rs @@ -1,33 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::RadrootsNostrEvent; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsProfileEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsProfileEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsProfileEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub profile: RadrootsProfile, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsProfile { - pub name: String, - pub display_name: Option<String>, - pub nip05: Option<String>, - pub about: Option<String>, - pub website: Option<String>, - pub picture: Option<String>, - pub banner: Option<String>, - pub lud06: Option<String>, - pub lud16: Option<String>, - pub bot: Option<String>, -} diff --git a/src/events/reaction/models.rs b/src/events/reaction/models.rs @@ -1,25 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; -use crate::events::{lib::RadrootsNostrEventRef, RadrootsNostrEvent}; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsReactionEventIndex { - pub event: RadrootsNostrEvent, - pub metadata: RadrootsReactionEventMetadata, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsReactionEventMetadata { - pub id: String, - pub author: String, - pub published_at: u32, - pub reaction: RadrootsReaction, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsReaction { - pub root: RadrootsNostrEventRef, - pub content: String, -} diff --git a/src/lib.rs b/src/lib.rs @@ -1,6 +0,0 @@ -pub const KIND_JOB_REQUEST: u16 = 5300; -pub const KIND_JOB_RESPONSE: u16 = 6300; -pub const KIND_APPLICATION_HANDLER: u16 = 31990; - -pub mod models; -pub mod events; -\ No newline at end of file diff --git a/src/models/indexer.rs b/src/models/indexer.rs @@ -1,25 +0,0 @@ -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsIndexShardMetadata { - pub file: String, - pub count: u32, - pub first_id: String, - pub last_id: String, - pub first_published_at: u32, - pub last_published_at: u32, - pub sha256: String, -} - -#[typeshare] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RadrootsIndexManifest { - pub country: String, - pub total: u32, - pub shard_size: u32, - pub first_published_at: u32, - pub last_published_at: u32, - pub shards: Vec<RadrootsIndexShardMetadata>, -} -\ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs @@ -1 +0,0 @@ -pub mod indexer; diff --git a/turbo.json b/turbo.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://turborepo.com/schema.json", + "ui": "tui", + "tasks": { + "build": { + "dependsOn": [ + "^build" + ], + "inputs": [ + "$TURBO_DEFAULT$", + ".env*" + ], + "outputs": [ + "dist/**" + ] + }, + "lint": { + "dependsOn": [ + "^lint" + ] + }, + "check-types": { + "dependsOn": [ + "^check-types" + ] + }, + "dev": { + "cache": false, + "persistent": true + }, + "@radroots/core-bindings#build": { + "dependsOn": [ + "@radroots/dev#build", + "@radroots/tsconfig#build" + ] + }, + "@radroots/events-bindings#build": { + "dependsOn": [ + "@radroots/dev#build", + "@radroots/tsconfig#build", + "@radroots/core-bindings#build" + ] + }, + "@radroots/events-indexed-bindings#build": { + "dependsOn": [ + "@radroots/dev#build", + "@radroots/tsconfig#build" + ] + }, + "@radroots/trade-bindings#build": { + "dependsOn": [ + "@radroots/dev#build", + "@radroots/tsconfig#build", + "@radroots/core-bindings#build", + "@radroots/events-bindings#build" + ] + } + } +} +\ No newline at end of file