Merge branch 'develop' into iceshrimp_mastodon

This commit is contained in:
naskya 2024-06-12 21:18:30 +09:00
commit f6643325c0
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
74 changed files with 1216 additions and 1003 deletions

View file

@ -289,7 +289,7 @@ cargo:doc:
- cp ci/cargo/config.toml /usr/local/cargo/config.toml
script:
- cargo doc --document-private-items
- printf "window.ALL_CRATES = ['backend_rs', 'macro_rs'];" > target/doc/crates.js
- printf 'window.ALL_CRATES = ["backend_rs", "macros", "macros_impl"];' > target/doc/crates.js
- printf '<meta http-equiv="refresh" content="0; url=%s">' 'backend_rs' > target/doc/index.html
- cd target/doc
- npx --yes netlify-cli deploy --prod --site="${CARGO_DOC_SITE_ID}" --dir=.

18
Cargo.lock generated
View file

@ -210,7 +210,7 @@ dependencies = [
"idna",
"image",
"isahc",
"macro-rs",
"macros",
"napi",
"napi-build",
"napi-derive",
@ -1718,18 +1718,24 @@ dependencies = [
]
[[package]]
name = "macro-rs"
name = "macros"
version = "0.0.0"
dependencies = [
"convert_case",
"napi",
"napi-derive",
"macros-impl",
"proc-macro2",
"quote",
"serde",
"serde_json",
]
[[package]]
name = "macros-impl"
version = "0.0.0"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn 2.0.66",
"thiserror",
]
[[package]]

View file

@ -1,9 +1,10 @@
[workspace]
members = ["packages/backend-rs", "packages/macro-rs"]
members = ["packages/backend-rs", "packages/macro-rs/macros", "packages/macro-rs/macros-impl"]
resolver = "2"
[workspace.dependencies]
macro-rs = { path = "packages/macro-rs" }
macros = { path = "packages/macro-rs/macros" }
macros-impl = { path = "packages/macro-rs/macros-impl" }
napi = { git = "https://github.com/napi-rs/napi-rs.git", rev = "ca2cd5c35a0c39ec4a94e93c6c5695b681046df2" }
napi-derive = "2.16.5"

View file

@ -2,23 +2,23 @@
FROM docker.io/node:20-alpine as build
WORKDIR /firefish
# Copy only backend-rs pnpm-related files first, to cache efficiently
COPY package.json pnpm-workspace.yaml ./
COPY packages/backend-rs/package.json packages/backend-rs/package.json
COPY packages/backend-rs/npm/linux-x64-musl/package.json packages/backend-rs/npm/linux-x64-musl/package.json
COPY packages/backend-rs/npm/linux-arm64-musl/package.json packages/backend-rs/npm/linux-arm64-musl/package.json
# Install compilation dependencies
RUN apk update && apk add --no-cache build-base linux-headers curl ca-certificates python3 perl
RUN curl --proto '=https' --tlsv1.2 --silent --show-error --fail https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Copy only backend-rs dependency-related files first, to cache efficiently
COPY package.json pnpm-workspace.yaml ./
COPY packages/backend-rs/package.json packages/backend-rs/package.json
COPY packages/backend-rs/npm/linux-x64-musl/package.json packages/backend-rs/npm/linux-x64-musl/package.json
COPY packages/backend-rs/npm/linux-arm64-musl/package.json packages/backend-rs/npm/linux-arm64-musl/package.json
COPY packages/macro-rs packages/macro-rs/
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
COPY Cargo.toml Cargo.toml
COPY Cargo.lock Cargo.lock
COPY packages/backend-rs/Cargo.toml packages/backend-rs/Cargo.toml
COPY packages/backend-rs/src/lib.rs packages/backend-rs/src/
COPY packages/macro-rs/Cargo.toml packages/macro-rs/Cargo.toml
COPY packages/macro-rs/src/lib.rs packages/macro-rs/src/
# Configure pnpm, and install backend-rs dependencies
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
@ -26,7 +26,6 @@ RUN cargo fetch --locked --manifest-path Cargo.toml
# Copy in the rest of the rust files
COPY packages/backend-rs packages/backend-rs/
# COPY packages/macro-rs packages/macro-rs/
# Compile backend-rs
RUN NODE_ENV='production' pnpm run --filter backend-rs build

View file

@ -12,7 +12,7 @@ napi = ["dep:napi", "dep:napi-derive", "dep:napi-build"]
crate-type = ["cdylib", "lib"]
[dependencies]
macro-rs = { workspace = true }
macros = { workspace = true }
napi = { workspace = true, optional = true, features = ["chrono_date", "napi4", "serde-json", "tokio_rt"] }
napi-derive = { workspace = true, optional = true }

View file

@ -1393,6 +1393,18 @@ export interface PackedEmoji {
height: number | null
}
export function publishToBroadcastStream(emoji: PackedEmoji): Promise<void>
export enum DriveFileEvent {
Create = 0,
Update = 1,
Delete = 2
}
export enum DriveFolderEvent {
Create = 0,
Update = 1,
Delete = 2
}
export function publishToDriveFileStream(userId: string, kind: DriveFileEvent, object: any): Promise<void>
export function publishToDriveFolderStream(userId: string, kind: DriveFolderEvent, object: any): Promise<void>
export function publishToGroupChatStream(groupId: string, kind: ChatEvent, object: any): Promise<void>
export interface AbuseUserReportLike {
id: string
@ -1401,6 +1413,7 @@ export interface AbuseUserReportLike {
comment: string
}
export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): Promise<void>
export function publishToNotesStream(note: Note): Promise<void>
export enum ChatEvent {
Message = 0,
Read = 1,

View file

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, updateNodeinfoCache, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, sqlRegexEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, PushSubscriptionType, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, updateNodeinfoCache, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, sqlRegexEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, PushSubscriptionType, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, DriveFileEvent, DriveFolderEvent, publishToDriveFileStream, publishToDriveFolderStream, publishToGroupChatStream, publishToModerationStream, publishToNotesStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
module.exports.SECOND = SECOND
module.exports.MINUTE = MINUTE
@ -389,8 +389,13 @@ module.exports.publishToChatStream = publishToChatStream
module.exports.ChatIndexEvent = ChatIndexEvent
module.exports.publishToChatIndexStream = publishToChatIndexStream
module.exports.publishToBroadcastStream = publishToBroadcastStream
module.exports.DriveFileEvent = DriveFileEvent
module.exports.DriveFolderEvent = DriveFolderEvent
module.exports.publishToDriveFileStream = publishToDriveFileStream
module.exports.publishToDriveFolderStream = publishToDriveFolderStream
module.exports.publishToGroupChatStream = publishToGroupChatStream
module.exports.publishToModerationStream = publishToModerationStream
module.exports.publishToNotesStream = publishToNotesStream
module.exports.ChatEvent = ChatEvent
module.exports.getTimestamp = getTimestamp
module.exports.genId = genId

View file

@ -1,17 +1,17 @@
//! This module is used in the TypeScript backend only.
#[crate::ts_export]
#[macros::ts_export]
pub const SECOND: i32 = 1000;
#[crate::ts_export]
#[macros::ts_export]
pub const MINUTE: i32 = 60 * SECOND;
#[crate::ts_export]
#[macros::ts_export]
pub const HOUR: i32 = 60 * MINUTE;
#[crate::ts_export]
#[macros::ts_export]
pub const DAY: i32 = 24 * HOUR;
#[crate::ts_export]
#[macros::ts_export]
pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
#[crate::ts_export]
#[macros::ts_export]
pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
/// List of file types allowed to be viewed directly in the browser
@ -21,7 +21,7 @@ pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
/// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
/// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
/// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
#[crate::ts_export]
#[macros::ts_export]
pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
// Images
"image/png",

View file

@ -11,12 +11,12 @@ fn set_cache(meta: &Meta) {
let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
}
#[crate::export(js_name = "fetchMeta")]
#[macros::export(js_name = "fetchMeta")]
pub async fn local_server_info() -> Result<Meta, DbErr> {
local_server_info_impl(true).await
}
#[crate::export(js_name = "updateMetaCache")]
#[macros::export(js_name = "updateMetaCache")]
pub async fn update() -> Result<(), DbErr> {
local_server_info_impl(false).await?;
Ok(())
@ -49,7 +49,7 @@ async fn local_server_info_impl(use_cache: bool) -> Result<Meta, DbErr> {
Ok(meta)
}
#[crate::export(object)]
#[macros::export(object)]
pub struct PugArgs {
pub img: Option<String>,
pub title: String,
@ -62,7 +62,7 @@ pub struct PugArgs {
pub private_mode: Option<bool>,
}
#[crate::ts_export]
#[macros::ts_export]
pub fn meta_to_pug_args(meta: Meta) -> PugArgs {
use rand::prelude::*;
let mut rng = rand::thread_rng();

View file

@ -4,11 +4,11 @@ use once_cell::sync::Lazy;
use serde::Deserialize;
use std::{env, fs};
pub const VERSION: &str = macro_rs::read_version_from_package_json!();
pub const VERSION: &str = macros::read_version_from_package_json!();
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
struct ServerConfig {
pub url: String,
pub port: u16,
@ -73,7 +73,7 @@ struct ServerConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct DbConfig {
pub host: String,
pub port: u16,
@ -86,7 +86,7 @@ pub struct DbConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct RedisConfig {
pub host: String,
pub port: u16,
@ -101,13 +101,13 @@ pub struct RedisConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct TlsConfig {
pub host: String,
pub reject_unauthorized: bool,
}
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct WorkerConfig {
pub web: u32,
pub queue: u32,
@ -115,7 +115,7 @@ pub struct WorkerConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct WorkerConfigInternal {
pub web: Option<u32>,
pub queue: Option<u32>,
@ -123,7 +123,7 @@ pub struct WorkerConfigInternal {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct IdConfig {
pub length: Option<u8>,
pub fingerprint: Option<String>,
@ -131,7 +131,7 @@ pub struct IdConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct SysLogConfig {
pub host: String,
pub port: u16,
@ -139,7 +139,7 @@ pub struct SysLogConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct DeepLConfig {
pub managed: Option<bool>,
pub auth_key: Option<String>,
@ -148,7 +148,7 @@ pub struct DeepLConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct LibreTranslateConfig {
pub managed: Option<bool>,
pub api_url: Option<String>,
@ -157,7 +157,7 @@ pub struct LibreTranslateConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct EmailConfig {
pub managed: Option<bool>,
pub address: Option<String>,
@ -170,7 +170,7 @@ pub struct EmailConfig {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct ObjectStorageConfig {
pub managed: Option<bool>,
pub base_url: Option<String>,
@ -186,7 +186,7 @@ pub struct ObjectStorageConfig {
pub s3_force_path_style: Option<bool>,
}
#[crate::export(object, use_nullable = false)]
#[macros::export(object, use_nullable = false)]
pub struct Config {
// ServerConfig (from default.yml)
pub url: String,
@ -263,7 +263,7 @@ fn read_config_file() -> ServerConfig {
data
}
#[crate::export]
#[macros::export]
pub fn load_config() -> Config {
let server_config = read_config_file();
let version = VERSION.to_owned();

View file

@ -234,6 +234,7 @@ mod unit_test {
use pretty_assertions::assert_eq;
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn set_get_expire() {
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)]
struct Data {
@ -278,6 +279,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn use_category() {
let key_1 = "fire";
let key_2 = "fish";

View file

@ -42,6 +42,7 @@ mod unit_test {
use sea_orm::{prelude::*, DbBackend, Statement};
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
async fn connect_sequential() {
get_conn().await.unwrap();
get_conn().await.unwrap();
@ -51,12 +52,14 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
async fn connect_concurrent() {
let [c1, c2, c3, c4, c5] = [get_conn(), get_conn(), get_conn(), get_conn(), get_conn()];
let _ = tokio::try_join!(c1, c2, c3, c4, c5).unwrap();
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
async fn connect_spawn() {
let mut tasks = Vec::new();
@ -69,6 +72,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `geteuid` on OS `linux`
async fn access() {
// DO NOT write any raw SQL query in the actual program
// (with the exception of PGroonga features)

View file

@ -119,6 +119,7 @@ mod unit_test {
use redis::AsyncCommands;
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn connect_sequential() {
get_conn().await.unwrap();
get_conn().await.unwrap();
@ -128,12 +129,14 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn connect_concurrent() {
let [c1, c2, c3, c4, c5] = [get_conn(), get_conn(), get_conn(), get_conn(), get_conn()];
let _ = tokio::try_join!(c1, c2, c3, c4, c5).unwrap();
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn connect_spawn() {
let mut tasks = Vec::new();
@ -146,6 +149,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn access() {
let mut redis = get_conn().await.unwrap();

View file

@ -1,7 +1,7 @@
use std::{fmt, str::FromStr};
#[cfg_attr(test, derive(Debug, PartialEq))]
#[crate::export(object)]
#[macros::export(object)]
pub struct Acct {
pub username: String,
pub host: Option<String>,
@ -51,12 +51,12 @@ impl From<Acct> for String {
}
}
#[crate::ts_export]
#[macros::ts_export]
pub fn string_to_acct(acct: &str) -> Acct {
Acct::from_str(acct).unwrap()
}
#[crate::ts_export]
#[macros::ts_export]
pub fn acct_to_string(acct: &Acct) -> String {
acct.to_string()
}

View file

@ -91,7 +91,7 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
type Nodeinfo = Nodeinfo20;
/// Fetches and returns the NodeInfo (version 2.0) of a remote server.
#[crate::export]
#[macros::export]
pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
tracing::info!("fetching from {}", host);
let links = fetch_nodeinfo_links(host).await?;
@ -156,6 +156,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `curl_global_init` on OS `linux`
async fn fetch_nodeinfo() {
assert_eq!(
super::fetch_nodeinfo("info.firefish.dev")

View file

@ -161,17 +161,17 @@ pub enum Error {
Json(#[from] serde_json::Error),
}
#[crate::ts_export(js_name = "nodeinfo_2_1")]
#[macros::ts_export(js_name = "nodeinfo_2_1")]
pub async fn nodeinfo_2_1_as_json() -> Result<serde_json::Value, Error> {
Ok(serde_json::to_value(nodeinfo_2_1().await?)?)
}
#[crate::ts_export(js_name = "nodeinfo_2_0")]
#[macros::ts_export(js_name = "nodeinfo_2_0")]
pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
Ok(serde_json::to_value(nodeinfo_2_0().await?)?)
}
#[crate::ts_export(js_name = "updateNodeinfoCache")]
#[macros::ts_export(js_name = "updateNodeinfoCache")]
pub async fn update_cache() -> Result<(), DbErr> {
nodeinfo_2_1_impl(false).await?;
Ok(())

View file

@ -34,7 +34,7 @@ pub struct Nodeinfo21 {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object, js_name = "Nodeinfo")]
#[macros::export(object, js_name = "Nodeinfo")]
pub struct Nodeinfo20 {
/// The schema version, must be 2.0.
pub version: String,
@ -71,7 +71,7 @@ pub struct Software21 {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct Software20 {
/// The canonical name of this server software.
pub name: String,
@ -82,7 +82,7 @@ pub struct Software20 {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
#[crate::derive_clone_and_export]
#[macros::derive_clone_and_export]
pub enum Protocol {
Activitypub,
Buddycloud,
@ -100,7 +100,7 @@ pub enum Protocol {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct Services {
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
pub inbound: Vec<Inbound>,
@ -112,7 +112,7 @@ pub struct Services {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
#[crate::derive_clone_and_export]
#[macros::derive_clone_and_export]
pub enum Inbound {
#[serde(rename = "atom1.0")]
Atom1,
@ -131,7 +131,7 @@ pub enum Inbound {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
#[crate::derive_clone_and_export]
#[macros::derive_clone_and_export]
pub enum Outbound {
#[serde(rename = "atom1.0")]
Atom1,
@ -169,7 +169,7 @@ pub enum Outbound {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct Usage {
pub users: Users,
pub local_posts: Option<u32>,
@ -180,7 +180,7 @@ pub struct Usage {
#[cfg_attr(test, derive(Debug, PartialEq))]
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct Users {
pub total: Option<u32>,
pub active_halfyear: Option<u32>,

View file

@ -12,7 +12,7 @@ const GREETING_MESSAGE: &str = "\
";
/// Prints the greeting message and the Firefish version to stdout.
#[crate::export]
#[macros::export]
pub fn greet() {
println!("{}", GREETING_MESSAGE);

View file

@ -3,7 +3,7 @@ use tracing::Level;
use tracing_subscriber::FmtSubscriber;
/// Initializes the [tracing] logger.
#[crate::export(js_name = "initializeRustLogger")]
#[macros::export(js_name = "initializeRustLogger")]
pub fn initialize_logger() {
let mut builder = FmtSubscriber::builder();

View file

@ -20,7 +20,7 @@ pub fn system_info() -> &'static std::sync::Mutex<System> {
}
/// Prints the server hardware information as the server info log.
#[crate::export]
#[macros::export]
pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
let system_info = system_info().lock()?;

View file

@ -1,5 +1,3 @@
use macro_rs::{derive_clone_and_export, export, ts_export};
pub mod config;
pub mod database;
pub mod federation;

View file

@ -18,7 +18,7 @@
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
#[macros::ts_export]
pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
Ok(crate::config::local_server_info()
.await?
@ -45,7 +45,7 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
#[macros::ts_export]
pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
Ok(crate::config::local_server_info()
.await?
@ -73,7 +73,7 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
/// # Ok(())
/// # }
/// ```
#[crate::ts_export]
#[macros::ts_export]
pub async fn is_allowed_server(host: &str) -> Result<bool, sea_orm::DbErr> {
let meta = crate::config::local_server_info().await?;

View file

@ -3,7 +3,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::DbErr;
#[crate::export(object)]
#[macros::export(object)]
pub struct PartialNoteToCheckWordMute {
pub file_ids: Vec<String>,
pub text: Option<String>,
@ -49,7 +49,7 @@ fn check_word_mute_impl(
/// * `note` : [PartialNoteToCheckWordMute] object
/// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
/// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
#[crate::export]
#[macros::export]
pub async fn check_word_mute(
note: PartialNoteToCheckWordMute,
muted_words: &[String],

View file

@ -13,7 +13,7 @@ pub enum Error {
NoHostname,
}
#[crate::ts_export]
#[macros::ts_export]
pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String, Error> {
Ok(match host {
Some(host) => format!("{}@{}", username, to_puny(host)?),
@ -21,7 +21,7 @@ pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String,
})
}
#[crate::ts_export]
#[macros::ts_export]
pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
Ok(match host {
Some(host) => extract_host(&crate::config::CONFIG.url)? == to_puny(host)?,
@ -29,12 +29,12 @@ pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
})
}
#[crate::ts_export]
#[macros::ts_export]
pub fn is_same_origin(uri: &str) -> Result<bool, Error> {
Ok(url::Url::parse(uri)?.origin().ascii_serialization() == crate::config::CONFIG.url)
}
#[crate::ts_export]
#[macros::ts_export]
pub fn extract_host(uri: &str) -> Result<String, Error> {
url::Url::parse(uri)?
.host_str()
@ -42,7 +42,7 @@ pub fn extract_host(uri: &str) -> Result<String, Error> {
.and_then(|v| Ok(to_puny(v)?))
}
#[crate::ts_export]
#[macros::ts_export]
pub fn to_puny(host: &str) -> Result<String, idna::Errors> {
idna::domain_to_ascii(host)
}

View file

@ -1,6 +1,6 @@
//! This module is used in the TypeScript backend only.
#[crate::ts_export]
#[macros::ts_export]
pub fn is_unicode_emoji(s: &str) -> bool {
emojis::get(s).is_some()
}

View file

@ -2,19 +2,19 @@ use once_cell::sync::Lazy;
use regex::Regex;
/// Escapes `%` and `\` in the given string.
#[crate::export]
#[macros::export]
pub fn sql_like_escape(src: &str) -> String {
src.replace('%', r"\%").replace('_', r"\_")
}
#[crate::export]
#[macros::export]
pub fn sql_regex_escape(src: &str) -> String {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[!$()*+.:<=>?\[\]\^{|}-]").unwrap());
RE.replace_all(src, r"\$1").to_string()
}
/// Returns `true` if `src` does not contain suspicious characters like `%`.
#[crate::export]
#[macros::export]
pub fn safe_for_sql(src: &str) -> bool {
!src.contains([
'\0', '\x08', '\x09', '\x1a', '\n', '\r', '"', '\'', '\\', '%',

View file

@ -1,5 +1,5 @@
/// Converts milliseconds to a human readable string.
#[crate::export]
#[macros::export]
pub fn format_milliseconds(milliseconds: u32) -> String {
let mut seconds = milliseconds / 1000;
let mut minutes = seconds / 60;

View file

@ -44,13 +44,13 @@ const BROWSER_SAFE_IMAGE_TYPES: [ImageFormat; 8] = [
static MTX_GUARD: Mutex<()> = Mutex::const_new(());
#[cfg_attr(test, derive(Debug, PartialEq))]
#[crate::export(object)]
#[macros::export(object)]
pub struct ImageSize {
pub width: u32,
pub height: u32,
}
#[crate::export]
#[macros::export]
pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
let attempted: bool;
@ -134,6 +134,7 @@ mod unit_test {
use pretty_assertions::assert_eq;
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn get_image_size_from_url() {
let png_url_1 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/splash.png";
let png_url_2 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/notification-badges/at.png";
@ -219,6 +220,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn too_many_attempts() {
let url = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/splash.png";

View file

@ -1,5 +1,5 @@
// TODO?: handle name collisions
#[crate::export(object, js_name = "NoteLikeForIsQuote")]
#[macros::export(object, js_name = "NoteLikeForIsQuote")]
pub struct NoteLike {
pub renote_id: Option<String>,
pub text: Option<String>,
@ -7,7 +7,7 @@ pub struct NoteLike {
pub file_ids: Vec<String>,
}
#[crate::export]
#[macros::export]
pub fn is_quote(note: &NoteLike) -> bool {
note.renote_id.is_some() && (note.text.is_some() || note.has_poll || !note.file_ids.is_empty())
}

View file

@ -1,4 +1,4 @@
#[crate::export]
#[macros::export]
pub fn is_safe_url(url: &str) -> bool {
if let Ok(url) = url.parse::<url::Url>() {
if url.host_str().unwrap_or_default() == "unix"

View file

@ -46,7 +46,7 @@ async fn get_latest_version() -> Result<String, Error> {
}
/// Returns the latest Firefish version.
#[crate::export]
#[macros::export]
pub async fn latest_version() -> Result<String, Error> {
let version: Option<String> =
cache::get_one(cache::Category::FetchUrl, UPSTREAM_PACKAGE_JSON_URL).await?;
@ -99,6 +99,7 @@ mod unit_test {
}
#[tokio::test]
#[cfg_attr(miri, ignore)] // can't call foreign function `getaddrinfo` on OS `linux`
async fn get_latest_version() {
// delete caches in case you run this test multiple times
cache::delete_one(cache::Category::FetchUrl, UPSTREAM_PACKAGE_JSON_URL)

View file

@ -1,4 +1,4 @@
#[crate::export(js_name = "getNoteSummary")]
#[macros::export(js_name = "getNoteSummary")]
pub fn summarize_impl(
file_ids: &[String],
text: Option<String>,

View file

@ -20,7 +20,7 @@ use regex::{Captures, Regex};
/// # use backend_rs::misc::nyaify::nyaify;
/// assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
/// ```
#[crate::export]
#[macros::export]
pub fn nyaify(text: &str, lang: Option<&str>) -> String {
let mut to_return = text.to_owned();

View file

@ -7,7 +7,7 @@ use argon2::{
};
/// Hashes the given password using [argon2] algorithm.
#[crate::export]
#[macros::export]
pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
let salt = SaltString::generate(&mut OsRng);
Ok(Argon2::default()
@ -26,7 +26,7 @@ pub enum Error {
}
/// Checks whether the given password and hash match.
#[crate::export]
#[macros::export]
pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
if is_old_password_algorithm(hash) {
Ok(bcrypt::verify(password, hash)?)
@ -40,7 +40,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
/// Returns whether the [bcrypt] algorithm is used for the password hash.
#[inline]
#[crate::export]
#[macros::export]
pub fn is_old_password_algorithm(hash: &str) -> bool {
// bcrypt hashes start with $2[ab]$
hash.starts_with("$2")
@ -51,6 +51,7 @@ mod unit_test {
use super::{hash_password, is_old_password_algorithm};
#[test]
#[cfg_attr(miri, ignore)] // too slow
fn verify_password() {
let password = "omWc*%sD^fn7o2cXmc9e2QasBdrbRuhNB*gx!J5";

View file

@ -5,14 +5,14 @@ use sea_orm::prelude::*;
use std::collections::HashMap;
#[cfg_attr(test, derive(PartialEq, Debug))]
#[crate::export(object)]
#[macros::export(object)]
pub struct DecodedReaction {
pub reaction: String,
pub name: Option<String>,
pub host: Option<String>,
}
#[crate::export]
#[macros::export]
pub fn decode_reaction(reaction: &str) -> DecodedReaction {
// Misskey allows you to include "+" and "-" in emoji shortcodes
// MFM spec: https://github.com/misskey-dev/mfm.js/blob/6aaf68089023c6adebe44123eebbc4dcd75955e0/docs/syntax.md?plain=1#L583
@ -38,7 +38,7 @@ pub fn decode_reaction(reaction: &str) -> DecodedReaction {
}
}
#[crate::export]
#[macros::export]
pub fn count_reactions(reactions: &HashMap<String, u32>) -> HashMap<String, u32> {
let mut res = HashMap::<String, u32>::new();
@ -62,7 +62,7 @@ pub enum Error {
Db(#[from] DbErr),
}
#[crate::export]
#[macros::export]
pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Result<String, Error> {
if let Some(reaction) = reaction {
// FIXME: Is it okay to do this only here?

View file

@ -5,7 +5,7 @@ use chrono::{Duration, Utc};
use sea_orm::prelude::*;
/// Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago
#[crate::export]
#[macros::export]
pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> {
let res = attestation_challenge::Entity::delete_many()
.filter(attestation_challenge::Column::CreatedAt.lt(Utc::now() - Duration::minutes(5)))

View file

@ -5,14 +5,14 @@ use sysinfo::{Disks, MemoryRefreshKind};
// TODO: i64 -> u64 (we can't export u64 to Node.js)
#[crate::export(object)]
#[macros::export(object)]
pub struct Cpu {
pub model: String,
// TODO: u16 -> usize (we can't export usize to Node.js)
pub cores: u16,
}
#[crate::export(object)]
#[macros::export(object)]
pub struct Memory {
/// Total memory amount in bytes
pub total: i64,
@ -22,7 +22,7 @@ pub struct Memory {
pub available: i64,
}
#[crate::export(object)]
#[macros::export(object)]
pub struct Storage {
/// Total storage space in bytes
pub total: i64,
@ -30,7 +30,7 @@ pub struct Storage {
pub used: i64,
}
#[crate::export]
#[macros::export]
pub fn cpu_info() -> Result<Cpu, SysinfoPoisonError> {
let system_info = system_info().lock()?;
@ -46,7 +46,7 @@ pub fn cpu_info() -> Result<Cpu, SysinfoPoisonError> {
})
}
#[crate::export]
#[macros::export]
pub fn cpu_usage() -> Result<f32, SysinfoPoisonError> {
let mut system_info = system_info().lock()?;
system_info.refresh_cpu_usage();
@ -57,7 +57,7 @@ pub fn cpu_usage() -> Result<f32, SysinfoPoisonError> {
Ok(total_cpu_usage / (cpu_threads as f32))
}
#[crate::export]
#[macros::export]
pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
let mut system_info = system_info().lock()?;
@ -70,7 +70,7 @@ pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
})
}
#[crate::export]
#[macros::export]
pub fn storage_usage() -> Option<Storage> {
// Get the first disk that is actualy used (has available space & has at least 1 GB total space).
let disks = Disks::new_with_refreshed_list();

View file

@ -37,7 +37,7 @@ pub enum Error {
// https://github.com/napi-rs/napi-rs/issues/2060
type Note = note::Model;
#[crate::export]
#[macros::export]
pub async fn update_antennas_on_new_note(
note: &Note,
note_author: &Acct,

View file

@ -1,6 +1,6 @@
//! This module is (currently) used in the TypeScript backend only.
#[crate::ts_export]
#[macros::ts_export]
pub async fn update_antenna_cache() -> Result<(), sea_orm::DbErr> {
super::cache::update().await?;
Ok(())

View file

@ -1,7 +1,7 @@
use crate::{database::db_conn, model::entity::note_watching, util::id::gen_id_at};
use sea_orm::{prelude::*, ActiveValue};
#[crate::export]
#[macros::export]
pub async fn watch_note(
watcher_id: &str,
note_author_id: &str,
@ -24,7 +24,7 @@ pub async fn watch_note(
Ok(())
}
#[crate::export]
#[macros::export]
pub async fn unwatch_note(watcher_id: &str, note_id: &str) -> Result<(), DbErr> {
let db = db_conn().await?;

View file

@ -42,7 +42,7 @@ fn get_client() -> Result<IsahcWebPushClient, Error> {
.cloned()?)
}
#[crate::export]
#[macros::export]
pub enum PushNotificationKind {
Generic,
Chat,
@ -243,7 +243,7 @@ async fn handle_web_push_failure(
Ok(())
}
#[crate::export]
#[macros::export]
pub async fn send_push_notification(
receiver_user_id: &str,
kind: PushNotificationKind,

View file

@ -3,8 +3,10 @@ pub mod channel;
pub mod chat;
pub mod chat_index;
pub mod custom_emoji;
pub mod drive;
pub mod group_chat;
pub mod moderation;
pub mod notes;
use crate::{
config::CONFIG,
@ -52,7 +54,7 @@ pub enum Stream {
},
}
#[crate::export]
#[macros::export]
pub enum ChatEvent {
Message,
Read,

View file

@ -1,6 +1,6 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[crate::export(js_name = "publishToChannelStream")]
#[macros::export(js_name = "publishToChannelStream")]
pub async fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
publish_to_stream(
&Stream::Channel { channel_id },

View file

@ -3,7 +3,7 @@ use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
// We want to merge `kind` and `object` into a single enum
// https://github.com/napi-rs/napi-rs/issues/2036
#[crate::export(js_name = "publishToChatStream")]
#[macros::export(js_name = "publishToChatStream")]
pub async fn publish(
sender_user_id: String,
receiver_user_id: String,

View file

@ -1,6 +1,6 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[crate::export]
#[macros::export]
pub enum ChatIndexEvent {
Message,
Read,
@ -9,7 +9,7 @@ pub enum ChatIndexEvent {
// We want to merge `kind` and `object` into a single enum
// https://github.com/napi-rs/napi-rs/issues/2036
#[crate::export(js_name = "publishToChatIndexStream")]
#[macros::export(js_name = "publishToChatIndexStream")]
pub async fn publish(
user_id: String,
kind: ChatIndexEvent,

View file

@ -4,7 +4,7 @@ use serde::Serialize;
// TODO: define schema type in other place
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct PackedEmoji {
pub id: String,
pub aliases: Vec<String>,
@ -17,7 +17,7 @@ pub struct PackedEmoji {
pub height: Option<i32>,
}
#[crate::export(js_name = "publishToBroadcastStream")]
#[macros::export(js_name = "publishToBroadcastStream")]
pub async fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
publish_to_stream(
&Stream::CustomEmoji,

View file

@ -0,0 +1,58 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[macros::export]
pub enum DriveFileEvent {
Create,
Update,
Delete,
}
#[macros::export]
pub enum DriveFolderEvent {
Create,
Update,
Delete,
}
// We want to merge `kind` and `object` into a single enum and merge the 2 functions
// https://github.com/napi-rs/napi-rs/issues/2036
#[macros::export(js_name = "publishToDriveFileStream")]
pub async fn publish_file(
user_id: String,
kind: DriveFileEvent,
object: &serde_json::Value, // file (create, update) or file id (delete)
) -> Result<(), Error> {
let kind = match kind {
DriveFileEvent::Create => "fileCreated",
DriveFileEvent::Update => "fileUpdated",
DriveFileEvent::Delete => "fileDeleted",
};
publish_to_stream(
&Stream::Drive { user_id },
Some(kind),
Some(serde_json::to_string(object)?),
)
.await
}
#[macros::export(js_name = "publishToDriveFolderStream")]
pub async fn publish_folder(
user_id: String,
kind: DriveFolderEvent,
object: &serde_json::Value, // folder (create, update) or folder id (delete)
) -> Result<(), Error> {
let kind = match kind {
DriveFolderEvent::Create => "folderCreated",
DriveFolderEvent::Update => "folderUpdated",
DriveFolderEvent::Delete => "folderDeleted",
};
publish_to_stream(
&Stream::Drive { user_id },
Some(kind),
Some(serde_json::to_string(object)?),
)
.await
}

View file

@ -3,7 +3,7 @@ use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
// We want to merge `kind` and `object` into a single enum
// https://github.com/napi-rs/napi-rs/issues/2036
#[crate::export(js_name = "publishToGroupChatStream")]
#[macros::export(js_name = "publishToGroupChatStream")]
pub async fn publish(
group_id: String,
kind: ChatEvent,

View file

@ -3,7 +3,7 @@ use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
#[crate::export(object)]
#[macros::export(object)]
pub struct AbuseUserReportLike {
pub id: String,
pub target_user_id: String,
@ -11,7 +11,7 @@ pub struct AbuseUserReportLike {
pub comment: String,
}
#[crate::export(js_name = "publishToModerationStream")]
#[macros::export(js_name = "publishToModerationStream")]
pub async fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
publish_to_stream(
&Stream::Moderation { moderator_id },

View file

@ -0,0 +1,13 @@
use crate::{
model::entity::note,
service::stream::{publish_to_stream, Error, Stream},
};
// for napi export
// https://github.com/napi-rs/napi-rs/issues/2060
type Note = note::Model;
#[macros::export(js_name = "publishToNotesStream")]
pub async fn publish(note: &Note) -> Result<(), Error> {
publish_to_stream(&Stream::Notes, None, Some(serde_json::to_string(note)?)).await
}

View file

@ -70,21 +70,13 @@ mod unit_test {
let expected_message_1 = "
raw: Error1(InnerError1)
message: error 1 occured
caused by: inner error 1
";
caused by: inner error 1";
let expected_message_2 = r#"
raw: Error2(InnerError2("foo"))
message: error 2 occured
caused by: unexpected string 'foo'
"#;
caused by: unexpected string 'foo'"#;
assert_eq!(
error_message_1,
expected_message_1[1..expected_message_1.len() - 1]
);
assert_eq!(
error_message_2,
expected_message_2[1..expected_message_2.len() - 1]
);
assert_eq!(error_message_1, expected_message_1[1..]);
assert_eq!(error_message_2, expected_message_2[1..]);
}
}

View file

@ -52,7 +52,7 @@ pub struct InvalidIdError {
id: String,
}
#[crate::export]
#[macros::export]
pub fn get_timestamp(id: &str) -> Result<i64, InvalidIdError> {
let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
if let Some(n) = n {
@ -68,13 +68,13 @@ pub fn get_timestamp(id: &str) -> Result<i64, InvalidIdError> {
/// in the same millisecond to reach 50% chance of collision.
///
/// Ref: <https://github.com/paralleldrive/cuid2#parameterized-length>
#[crate::export]
#[macros::export]
pub fn gen_id() -> String {
create_id(&Utc::now().naive_utc())
}
/// Generate an ID using a specific datetime
#[crate::export]
#[macros::export]
pub fn gen_id_at(date: DateTime<Utc>) -> String {
create_id(&date.naive_utc())
}

View file

@ -3,7 +3,7 @@
use rand::{distributions::Alphanumeric, thread_rng, Rng};
/// Generates a random string based on [thread_rng] and [Alphanumeric].
#[crate::export]
#[macros::export]
pub fn generate_secure_random_string(length: u16) -> String {
thread_rng()
.sample_iter(Alphanumeric)
@ -12,7 +12,7 @@ pub fn generate_secure_random_string(length: u16) -> String {
.collect()
}
#[crate::export]
#[macros::export]
pub fn generate_user_token() -> String {
generate_secure_random_string(16)
}

View file

@ -31,18 +31,18 @@
"@koa/router": "12.0.1",
"@ladjs/koa-views": "9.0.0",
"@peertube/http-signature": "1.7.0",
"@redocly/openapi-core": "1.14.0",
"@redocly/openapi-core": "1.15.0",
"@sinonjs/fake-timers": "11.2.2",
"adm-zip": "0.5.14",
"ajv": "8.16.0",
"archiver": "7.0.1",
"async-lock": "1.4.0",
"async-mutex": "0.5.0",
"aws-sdk": "2.1638.0",
"aws-sdk": "2.1639.0",
"axios": "1.7.2",
"backend-rs": "workspace:*",
"blurhash": "2.0.5",
"bull": "4.12.9",
"bull": "4.13.0",
"cacheable-lookup": "git+https://github.com/TheEssem/cacheable-lookup.git#dd2fb616366a3c68dcf321a57a67295967b204bf",
"cbor-x": "1.5.9",
"chalk": "5.3.0",
@ -123,7 +123,7 @@
},
"devDependencies": {
"@swc/cli": "0.3.12",
"@swc/core": "1.5.27",
"@swc/core": "1.5.28",
"@types/adm-zip": "0.5.5",
"@types/async-lock": "1.4.0",
"@types/color-convert": "2.0.3",
@ -179,7 +179,7 @@
"tsconfig-paths": "4.2.0",
"type-fest": "4.20.0",
"typescript": "5.4.5",
"webpack": "5.91.0",
"webpack": "5.92.0",
"ws": "8.17.0"
}
}

View file

@ -1,5 +1,5 @@
import { deleteFile } from "@/services/drive/delete-file.js";
import { publishDriveStream } from "@/services/stream.js";
import { publishToDriveFileStream, DriveFileEvent } from "backend-rs";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { DriveFiles } from "@/models/index.js";
@ -51,5 +51,5 @@ export default define(meta, paramDef, async (ps, user) => {
await deleteFile(file);
// Publish fileDeleted event
publishDriveStream(user.id, "fileDeleted", file.id);
publishToDriveFileStream(user.id, DriveFileEvent.Delete, file.id);
});

View file

@ -1,8 +1,9 @@
import { publishDriveStream } from "@/services/stream.js";
import { publishToDriveFileStream, DriveFileEvent } from "backend-rs";
import { DriveFiles, DriveFolders } from "@/models/index.js";
import { config } from "@/config.js";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { toRustObject } from "@/prelude/undefined-to-null.js";
export const meta = {
tags: ["drive"],
@ -110,7 +111,7 @@ export default define(meta, paramDef, async (ps, user) => {
const fileObj = await DriveFiles.pack(file, { self: true });
// Publish fileUpdated event
publishDriveStream(user.id, "fileUpdated", fileObj);
publishToDriveFileStream(user.id, DriveFileEvent.Update, toRustObject(file));
return fileObj;
});

View file

@ -1,8 +1,9 @@
import { publishDriveStream } from "@/services/stream.js";
import { publishToDriveFolderStream, DriveFolderEvent } from "backend-rs";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { DriveFolders } from "@/models/index.js";
import { genIdAt } from "backend-rs";
import { toRustObject } from "@/prelude/undefined-to-null.js";
export const meta = {
tags: ["drive"],
@ -64,7 +65,11 @@ export default define(meta, paramDef, async (ps, user) => {
const folderObj = await DriveFolders.pack(folder);
// Publish folderCreated event
publishDriveStream(user.id, "folderCreated", folderObj);
publishToDriveFolderStream(
user.id,
DriveFolderEvent.Create,
toRustObject(folder),
);
return folderObj;
});

View file

@ -1,5 +1,5 @@
import define from "@/server/api/define.js";
import { publishDriveStream } from "@/services/stream.js";
import { publishToDriveFolderStream, DriveFolderEvent } from "backend-rs";
import { ApiError } from "@/server/api/error.js";
import { DriveFolders, DriveFiles } from "@/models/index.js";
@ -56,5 +56,5 @@ export default define(meta, paramDef, async (ps, user) => {
await DriveFolders.delete(folder.id);
// Publish folderCreated event
publishDriveStream(user.id, "folderDeleted", folder.id);
publishToDriveFolderStream(user.id, DriveFolderEvent.Delete, folder.id);
});

View file

@ -1,7 +1,8 @@
import { publishDriveStream } from "@/services/stream.js";
import { publishToDriveFolderStream, DriveFolderEvent } from "backend-rs";
import define from "@/server/api/define.js";
import { ApiError } from "@/server/api/error.js";
import { DriveFolders } from "@/models/index.js";
import { toRustObject } from "@/prelude/undefined-to-null.js";
export const meta = {
tags: ["drive"],
@ -112,7 +113,11 @@ export default define(meta, paramDef, async (ps, user) => {
const folderObj = await DriveFolders.pack(folder);
// Publish folderUpdated event
publishDriveStream(user.id, "folderUpdated", folderObj);
publishToDriveFolderStream(
user.id,
DriveFolderEvent.Update,
toRustObject(folder),
);
return folderObj;
});

View file

@ -5,8 +5,14 @@ import { v4 as uuid } from "uuid";
import type S3 from "aws-sdk/clients/s3.js"; // TODO: migrate to SDK v3
import sharp from "sharp";
import { IsNull } from "typeorm";
import { publishMainStream, publishDriveStream } from "@/services/stream.js";
import { FILE_TYPE_BROWSERSAFE, fetchMeta, genId } from "backend-rs";
import { publishMainStream } from "@/services/stream.js";
import {
DriveFileEvent,
FILE_TYPE_BROWSERSAFE,
fetchMeta,
genId,
publishToDriveFileStream,
} from "backend-rs";
import { contentDisposition } from "@/misc/content-disposition.js";
import { getFileInfo } from "@/misc/get-file-info.js";
import {
@ -28,6 +34,7 @@ import { driveLogger } from "./logger.js";
import { GenerateVideoThumbnail } from "./generate-video-thumbnail.js";
import { deleteFile } from "./delete-file.js";
import { inspect } from "node:util";
import { toRustObject } from "@/prelude/undefined-to-null.js";
const logger = driveLogger.createSubLogger("register", "yellow");
@ -658,11 +665,15 @@ export async function addFile({
logger.info(`drive file has been created ${file.id}`);
if (user) {
if (user != null) {
DriveFiles.pack(file, { self: true }).then((packedFile) => {
// Publish driveFileCreated event
publishMainStream(user.id, "driveFileCreated", packedFile);
publishDriveStream(user.id, "fileCreated", packedFile);
publishToDriveFileStream(
user.id,
DriveFileEvent.Create,
toRustObject(file),
);
});
}

View file

@ -1,9 +1,5 @@
import * as mfm from "mfm-js";
import {
publishMainStream,
publishNotesStream,
publishNoteStream,
} from "@/services/stream.js";
import { publishMainStream, publishNoteStream } from "@/services/stream.js";
import DeliverManager from "@/remote/activitypub/deliver-manager.js";
import renderNote from "@/remote/activitypub/renderer/note.js";
import renderCreate from "@/remote/activitypub/renderer/create.js";
@ -49,6 +45,7 @@ import {
genIdAt,
isQuote,
isSilencedServer,
publishToNotesStream,
} from "backend-rs";
import { countSameRenotes } from "@/misc/count-same-renotes.js";
import { deliverToRelays, getCachedRelays } from "../relay.js";
@ -661,7 +658,7 @@ export default async (
30,
);
}
publishNotesStream(noteToPublish);
publishToNotesStream(toRustObject(noteToPublish));
}
} finally {
await lock.release();

View file

@ -12,7 +12,7 @@ import type {
// AntennaStreamTypes,
// BroadcastTypes,
// ChannelStreamTypes,
DriveStreamTypes,
// DriveStreamTypes,
// GroupMessagingStreamTypes,
InternalStreamTypes,
MainStreamTypes,
@ -89,17 +89,18 @@ class Publisher {
);
};
public publishDriveStream = <K extends keyof DriveStreamTypes>(
userId: User["id"],
type: K,
value?: DriveStreamTypes[K],
): void => {
this.publish(
`driveStream:${userId}`,
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishDriveStream = <K extends keyof DriveStreamTypes>(
// userId: User["id"],
// type: K,
// value?: DriveStreamTypes[K],
// ): void => {
// this.publish(
// `driveStream:${userId}`,
// type,
// typeof value === "undefined" ? null : value,
// );
// };
public publishNoteStream = <K extends keyof NoteStreamTypes>(
noteId: Note["id"],
@ -201,9 +202,10 @@ class Publisher {
// );
// };
public publishNotesStream = (note: Note): void => {
this.publish("notesStream", null, note);
};
/* ported to backend-rs */
// public publishNotesStream = (note: Note): void => {
// this.publish("notesStream", null, note);
// };
/* ported to backend-rs */
// public publishAdminStream = <K extends keyof AdminStreamTypes>(
@ -227,9 +229,9 @@ export const publishInternalEvent = publisher.publishInternalEvent;
export const publishUserEvent = publisher.publishUserEvent;
// export const publishBroadcastStream = publisher.publishBroadcastStream;
export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream;
// export const publishDriveStream = publisher.publishDriveStream;
export const publishNoteStream = publisher.publishNoteStream;
export const publishNotesStream = publisher.publishNotesStream;
// export const publishNotesStream = publisher.publishNotesStream;
export const publishNoteUpdatesStream = publisher.publishNoteUpdatesStream;
// export const publishChannelStream = publisher.publishChannelStream;
export const publishUserListStream = publisher.publishUserListStream;

View file

@ -72,7 +72,7 @@
"qrcode-vue3": "1.6.8",
"rollup": "4.17.2",
"s-age": "1.1.2",
"sass": "1.77.4",
"sass": "1.77.5",
"seedrandom": "3.0.5",
"stringz": "2.1.0",
"swiper": "11.1.4",

View file

@ -22,7 +22,7 @@
},
"devDependencies": {
"@swc/cli": "0.3.12",
"@swc/core": "1.5.27",
"@swc/core": "1.5.28",
"@swc/types": "0.1.8",
"@types/jest": "29.5.12",
"@types/node": "20.14.2",

View file

@ -0,0 +1,11 @@
[package]
name = "macros-impl"
version = "0.0.0"
edition = "2021"
rust-version = "1.74"
[dependencies]
convert_case = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true, features = ["clone-impls", "extra-traits", "full", "parsing", "printing"] }

View file

@ -0,0 +1,4 @@
#![allow(clippy::items_after_test_module)]
pub mod napi;
mod util;

View file

@ -0,0 +1,457 @@
//! Napi related macros
use convert_case::{Case, Casing};
use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, ToTokens};
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
///
/// The macro is simply converted into `napi_derive::napi(...)`
/// if it is not applied to a function.
///
/// The macro sets the following attributes by default if not specified:
/// - `use_nullable = true` (if `object` or `constructor` attribute is specified)
/// - `js_name` to the camelCase version of the original function name (for functions)
///
/// The types of the function arguments is converted with following rules:
/// - `&str` and `&mut str` are converted to [`String`]
/// - `&[T]` and `&mut [T]` are converted to [`Vec<T>`]
/// - `&T` and `&mut T` are converted to `T`
/// - Other `T` remains `T`
///
/// In addition, return type [`Result<T>`] and [`Result<T, E>`] are converted to [`napi::Result<T>`](https://docs.rs/napi/latest/napi/type.Result.html).
/// Note that `E` must implement [std::error::Error] trait,
/// and `crate::util::error_chain::format_error(error: &dyn std::error::Error) -> String` function must be present.
///
/// # Examples
/// ## Applying the macro to a struct
/// ```
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi(object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// #[napi_derive::napi(use_nullable = true, object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
/// # });
/// ```
///
/// ## Function with explicitly specified `js_name`
/// ```
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi(js_name = "add1")]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
///
/// #[napi_derive::napi(js_name = "add1")]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// # });
/// ```
///
/// ## Function with `i32` argument
/// ```
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// #[napi_derive::napi(js_name = "addOne",)]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// # });
/// ```
///
/// ## Function with `&str` argument
/// ```
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi]
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
/// str1.to_owned() + str2
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
/// str1.to_owned() + str2
/// }
///
/// #[napi_derive::napi(js_name = "concatenateString",)]
/// pub fn concatenate_string_napi(str1: String, str2: String) -> String {
/// concatenate_string(&str1, &str2)
/// }
/// # });
/// ```
///
/// ## Function with `&[String]` argument
/// ```
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi]
/// pub fn string_array_length(array: &[String]) -> u32 {
/// array.len() as u32
/// }
///
/// # }, {
/// /******* the code above expands to *******/
///
/// pub fn string_array_length(array: &[String]) -> u32 {
/// array.len() as u32
/// }
///
/// #[napi_derive::napi(js_name = "stringArrayLength",)]
/// pub fn string_array_length_napi(array: Vec<String>) -> u32 {
/// string_array_length(&array)
/// }
/// # });
/// ```
///
/// ## Function with `Result<T, E>` return type
/// ```
/// # quote::quote! { // prevent compiling the code
/// #[derive(thiserror::Error, Debug)]
/// pub enum IntegerDivisionError {
/// #[error("Divided by zero")]
/// DividedByZero,
/// #[error("Not divisible with remainder {0}")]
/// NotDivisible(i64),
/// }
/// # };
///
/// # use macros_impl::napi::napi;
/// # macros_impl::macro_doctest!({
/// #[macros::napi]
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
/// match divisor {
/// 0 => Err(IntegerDivisionError::DividedByZero),
/// _ => match dividend % divisor {
/// 0 => Ok(dividend / divisor),
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
/// },
/// }
/// }
/// # }, {
///
/// /******* the function above expands to *******/
///
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
/// match divisor {
/// 0 => Err(IntegerDivisionError::DividedByZero),
/// _ => match dividend % divisor {
/// 0 => Ok(dividend / divisor),
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
/// },
/// }
/// }
///
/// #[napi_derive::napi(js_name = "integerDivide",)]
/// pub fn integer_divide_napi(dividend: i64, divisor: i64) -> napi::Result<i64> {
/// integer_divide(dividend, divisor)
/// .map_err(|err| napi::Error::from_reason(
/// format!("\n{}\n", crate::util::error_chain::format_error(&err))
/// ))
/// }
/// # });
/// ```
///
pub fn napi(macro_attr: TokenStream, item: TokenStream) -> TokenStream {
let macro_attr_tokens: Vec<TokenTree> = macro_attr.clone().into_iter().collect();
// generated extra macro attr TokenStream (prepended before original input `macro_attr`)
let mut extra_macro_attr = TokenStream::new();
let item: syn::Item =
syn::parse2(item).expect("Failed to parse input TokenStream to syn::Item");
// handle non-functions
let syn::Item::Fn(item_fn) = item else {
// set `use_nullable = true` if `object` or `constructor` present but not `use_nullable`
if macro_attr_tokens.iter().any(|token| {
matches!(token, TokenTree::Ident(ident) if ident == "object" || ident == "constructor")
}) && !macro_attr_tokens.iter().any(|token| {
matches!(token, TokenTree::Ident(ident) if ident == "use_nullable")
}) {
quote! { use_nullable = true, }.to_tokens(&mut extra_macro_attr);
}
return quote! {
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
#item
};
};
// handle functions
let ident = &item_fn.sig.ident;
let item_fn_attrs = &item_fn.attrs;
let item_fn_vis = &item_fn.vis;
let mut item_fn_sig = item_fn.sig.clone();
let mut function_call_modifiers = Vec::<TokenStream>::new();
// append "_napi" to function name
item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap();
// append `.await` to function call in async function
if item_fn_sig.asyncness.is_some() {
function_call_modifiers.push(quote! {
.await
});
}
// convert return type `...::Result<T, ...>` to `napi::Result<T>`
if let syn::ReturnType::Type(_, ref mut return_type) = item_fn_sig.output {
if let Some(result_generic_type) = (|| {
let syn::Type::Path(return_type_path) = &**return_type else {
return None;
};
// match a::b::c::Result
let last_segment = return_type_path.path.segments.last()?;
if last_segment.ident != "Result" {
return None;
};
// extract <T, ...> from Result<T, ...>
let syn::PathArguments::AngleBracketed(generic_arguments) = &last_segment.arguments
else {
return None;
};
// return T only
generic_arguments.args.first()
})() {
// modify return type
*return_type = syn::parse_quote! {
napi::Result<#result_generic_type>
};
// add modifier to function call result
function_call_modifiers.push(quote! {
.map_err(|err| napi::Error::from_reason(
format!("\n{}\n", crate::util::error_chain::format_error(&err))
))
});
}
};
// arguments in function call
let called_args: Vec<TokenStream> = item_fn_sig
.inputs
.iter_mut()
.map(|input| match input {
// self
syn::FnArg::Receiver(arg) => {
let mut tokens = TokenStream::new();
if let Some((ampersand, lifetime)) = &arg.reference {
ampersand.to_tokens(&mut tokens);
lifetime.to_tokens(&mut tokens);
}
arg.mutability.to_tokens(&mut tokens);
arg.self_token.to_tokens(&mut tokens);
tokens
}
// typed argument
syn::FnArg::Typed(arg) => {
match &mut *arg.pat {
syn::Pat::Ident(ident) => {
let name = &ident.ident;
match &*arg.ty {
// reference type argument => move ref from sigature to function call
syn::Type::Reference(r) => {
// add reference anotations to arguments in function call
let mut tokens = TokenStream::new();
r.and_token.to_tokens(&mut tokens);
if let Some(lifetime) = &r.lifetime {
lifetime.to_tokens(&mut tokens);
}
r.mutability.to_tokens(&mut tokens);
name.to_tokens(&mut tokens);
// modify napi argument types in function sigature
// (1) add `mut` token to `&mut` type
ident.mutability = r.mutability;
// (2) remove reference
*arg.ty = syn::Type::Verbatim(match &*r.elem {
syn::Type::Slice(slice) => {
let ty = &*slice.elem;
quote! { Vec<#ty> }
}
_ => {
let elem_tokens = r.elem.to_token_stream();
match elem_tokens.to_string().as_str() {
// &str => String
"str" => quote! { String },
// &T => T
_ => elem_tokens,
}
}
});
// return arguments in function call
tokens
}
// o.w., return it as is
_ => quote! { #name },
}
}
pat => panic!("Unexpected FnArg: {pat:#?}"),
}
}
})
.collect();
// handle macro attr
// set js_name if not specified
if !macro_attr_tokens
.iter()
.any(|token| matches!(token, TokenTree::Ident(ident) if ident == "js_name"))
{
let js_name = ident.to_string().to_case(Case::Camel);
quote! { js_name = #js_name, }.to_tokens(&mut extra_macro_attr);
}
quote! {
#item_fn
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
#(#item_fn_attrs)*
#item_fn_vis #item_fn_sig {
#ident(#(#called_args),*)
#(#function_call_modifiers)*
}
}
}
crate::macro_unit_tests! {
mut_ref_argument: {
#[macros::napi]
pub fn append_string_and_clone(
base_str: &mut String,
appended_str: &str,
) -> String {
base_str.push_str(appended_str);
base_str.to_owned()
}
} generates {
#[napi_derive::napi(js_name = "appendStringAndClone", )]
pub fn append_string_and_clone_napi(
mut base_str: String,
appended_str: String,
) -> String {
append_string_and_clone(&mut base_str, &appended_str)
}
}
result_return_type: {
#[macros::napi]
pub fn integer_divide(
dividend: i64,
divisor: i64,
) -> Result<i64, IntegerDivisionError> {
match divisor {
0 => Err(IntegerDivisionError::DividedByZero),
_ => match dividend % divisor {
0 => Ok(dividend / divisor),
remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
},
}
}
} generates {
#[napi_derive::napi(js_name = "integerDivide", )]
pub fn integer_divide_napi(
dividend: i64,
divisor: i64,
) -> napi::Result<i64> {
integer_divide(dividend, divisor)
.map_err(|err| napi::Error::from_reason(
format!("\n{}\n", crate::util::error_chain::format_error(&err))
))
}
}
async_function: {
#[macros::napi]
pub async fn async_add_one(x: i32) -> i32 {
x + 1
}
} generates {
#[napi_derive::napi(js_name = "asyncAddOne", )]
pub async fn async_add_one_napi(x: i32) -> i32 {
async_add_one(x)
.await
}
}
slice_type: {
#[macros::napi]
pub fn string_array_length(array: &[String]) -> u32 {
array.len() as u32
}
} generates {
#[napi_derive::napi(js_name = "stringArrayLength", )]
pub fn string_array_length_napi(array: Vec<String>) -> u32 {
string_array_length(&array)
}
}
object_with_explicitly_set_use_nullable: {
#[macros::napi(object, use_nullable = false)]
struct Person {
id: i32,
name: Option<String>,
}
} becomes {
#[napi_derive::napi(object, use_nullable = false)]
struct Person {
id: i32,
name: Option<String>,
}
}
macro_attr: {
#[macros::napi(ts_return_type = "number")]
pub fn add_one(x: i32) -> i32 {
x + 1
}
} generates {
#[napi_derive::napi(js_name = "addOne", ts_return_type = "number")]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
explicitly_specified_js_name_and_other_macro_attr: {
#[macros::napi(ts_return_type = "number", js_name = "add1")]
pub fn add_one(x: i32) -> i32 {
x + 1
}
} generates {
#[napi_derive::napi(ts_return_type = "number", js_name = "add1")]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
}

View file

@ -0,0 +1,3 @@
//! Utilities for developing procedural macros
mod tester;

View file

@ -0,0 +1,122 @@
//! Macros for testing procedural macros
/// Tests if the macro expands correctly.
///
/// # Examples
/// ```
/// use macros_impl::napi::napi;
///
/// macros_impl::macro_doctest!({
/// #[macros::napi(object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
/// }, {
/// #[napi_derive::napi(use_nullable = true, object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
/// });
/// ```
#[macro_export]
macro_rules! macro_doctest {
({
#[macros :: $macro_name:ident $(( $($attr:tt)* ))?]
$($item:tt)*
}, {
$($expanded:tt)*
}) => {
assert_eq!(
::std::string::ToString::to_string(
&$macro_name(
::quote::quote!($( $($attr)* )?),
::quote::quote!($($item)*),
)
),
::std::string::ToString::to_string(
&::quote::quote!($($expanded)*)
)
);
};
}
/// Creates unit tests for macros.
///
/// # Examples
/// ```
/// macros_impl::macro_unit_tests! {
/// add1_becomes: {
/// #[macros::napi(js_name = "add1")]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// } becomes { // the code above should expand to the following code
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
///
/// #[napi_derive::napi(js_name = "add1")]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// }
///
/// // this test case is equivalent to `add1_becomes`
/// add1_generates: {
/// #[macros::napi(js_name = "add1")]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// } generates { // the code above should generate the following code
/// #[napi_derive::napi(js_name = "add1")]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// }
/// }
/// ```
#[macro_export]
macro_rules! macro_unit_tests {
(@test $macro_name:ident($attr:ident, $item:ident) becomes $expanded:ident) => {
assert_eq!(
::std::format!("{}", $macro_name($attr, $item)),
::std::format!("{}", $expanded),
);
};
(@test $macro_name:ident($attr:ident, $item:ident) generates $expanded:ident) => {
let item_str = format!("{}", $item);
assert_eq!(
::std::format!("{}", $macro_name($attr, $item)),
::std::format!("{} {}", item_str, $expanded),
);
};
(
$(
$test_name:ident : {
#[macros :: $macro_name:ident $(( $($attr:tt)* ))?]
$($item:tt)*
} $op:tt {
$($expanded:tt)*
}
)*
) => {
#[cfg(test)]
mod unit_test {
use super::*;
$(
#[test]
fn $test_name() {
let attr = ::quote::quote!($( $($attr)* )?);
let item = ::quote::quote!($($item)*);
let expanded = ::quote::quote!($($expanded)*);
$crate::macro_unit_tests!(@test $macro_name(attr, item) $op expanded);
}
)*
}
};
}

View file

@ -1,5 +1,5 @@
[package]
name = "macro-rs"
name = "macros"
version = "0.0.0"
edition = "2021"
rust-version = "1.74"
@ -8,14 +8,9 @@ rust-version = "1.74"
proc-macro = true
[dependencies]
convert_case = { workspace = true }
macros-impl = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["std"] }
syn = { workspace = true, features = ["extra-traits", "full"] }
[dev-dependencies]
thiserror = { workspace = true }
napi = { workspace = true }
napi-derive = { workspace = true, features = ["noop"] }

View file

@ -0,0 +1,88 @@
//! Helper macros for developing procedural macros
#[doc(hidden)]
pub(crate) use quote::quote;
/// Defines wrapper #\[proc_macro_attribute]s.
///
/// # Examples
/// ```ignore
/// define_wrapper_proc_macro_attributes! {
/// // expand `#[export(attr)]` to
/// // ```
/// // #[cfg_attr(feature = "napi", macros::napi(#attr))]
/// // ```
/// export(attr, item) {
/// #[cfg_attr(feature = "napi", macros::napi(#attr))]
/// #item
/// }
///
/// // expand `#[ts_export(attr)]` to
/// // ```
/// // #[cfg(feature = "napi")]
/// // #[macros::napi(#attr)]
/// // ```
/// ts_export(attr, item) {
/// #[cfg(feature = "napi")]
/// #[macros::napi(#attr)]
/// #item
/// }
/// }
/// ```
macro_rules! define_wrapper_proc_macro_attributes {
(
$(
$(#[$meta:meta])*
$macro_name:ident ($arg_attr:ident, $arg_item:ident) {
$($body:tt)*
}
)*
) => {
$(
$(#[$meta])*
#[proc_macro_attribute]
pub fn $macro_name(
attr: ::proc_macro::TokenStream,
item: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
let $arg_attr: ::proc_macro2::TokenStream = attr.into();
let $arg_item: ::proc_macro2::TokenStream = item.into();
::quote::quote!($($body)*).into()
}
)*
}
}
pub(crate) use define_wrapper_proc_macro_attributes;
/// Wraps and exports #\[proc_macro_attribute] implementation.
///
/// # Examples
/// ```ignore
/// reexport_proc_macro_attributes! {
/// // wrap and export [macros_impl::napi::napi] as #[macros::napi]
/// macros_impl::napi::napi as napi
///
/// // wrap and export [macros_impl::errors::errors] as #[macros::errors]
/// macros_impl::errors::errors as errors
/// }
/// ```
macro_rules! reexport_proc_macro_attributes {
(
$(
$(#[$meta:meta])*
$impl_path:path as $macro_name:ident
)*
) => {
$(
$(#[$meta])*
#[proc_macro_attribute]
pub fn $macro_name(
attr: ::proc_macro::TokenStream,
item: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
$impl_path(attr.into(), item.into()).into()
}
)*
}
}
pub(crate) use reexport_proc_macro_attributes;

View file

@ -0,0 +1,81 @@
mod helper;
use helper::*;
/// Reads the version field in the project root package.json at compile time.
///
/// # Example
/// You can get a compile-time constant version number using this macro:
/// ```
/// # use macros::read_version_from_package_json;
/// // VERSION == "YYYYMMDD" (or "YYYYMMDD-X")
/// const VERSION: &str = read_version_from_package_json!();
/// ```
#[proc_macro]
pub fn read_version_from_package_json(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[derive(serde::Deserialize)]
struct PackageJson {
version: String,
}
let file = std::fs::File::open("package.json").expect("Failed to open package.json");
let json: PackageJson = serde_json::from_reader(file).unwrap();
let version = &json.version;
quote!(#version).into()
}
define_wrapper_proc_macro_attributes! {
/// Exports an enum to TypeScript, and derive [Clone].
///
/// You need this macro because [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
/// automatically derives the [Clone] trait for enums and causes conflicts.
///
/// This is a wrapper of [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
/// that expands to
/// ```no_run
/// #[cfg_attr(not(feature = "napi"), derive(Clone))]
/// #[cfg_attr(feature = "napi", napi_derive::napi(attr))]
/// # enum E {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s).
derive_clone_and_export(attr, item) {
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi(#attr))]
#item
}
/// Exports a function, struct, enum, const, etc. to TypeScript.
///
/// This is a wrapper of [macro@napi] that expands to
/// ```no_run
/// #[cfg_attr(feature = "napi", macros::napi(attr))]
/// # fn f() {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s). See [macro@napi] for more details.
export(attr, item) {
#[cfg_attr(feature = "napi", macros::napi(#attr))]
#item
}
/// Exports a function, struct, enum, const, etc. to TypeScript
/// and make it unable to use in Rust.
///
/// This is a wrapper of [macro@napi] that expands to
/// ```no_run
/// #[cfg(feature = "napi")]
/// #[macros::napi(attr)]
/// # fn f() {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s). See [macro@napi] for more details.
ts_export(attr, item) {
#[cfg(feature = "napi")]
#[macros::napi(#attr)]
#item
}
}
reexport_proc_macro_attributes! {
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
/// See [macros_impl::napi::napi] for details.
macros_impl::napi::napi as napi
}

View file

@ -1,680 +0,0 @@
use convert_case::{Case, Casing};
use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, ToTokens};
/// Read the version field in the project root package.json at compile time
///
/// # Example
/// You can get a compile-time constant version number using this macro:
/// ```
/// # use macro_rs::read_version_from_package_json;
/// // VERSION == "YYYYMMDD" (or "YYYYMMDD-X")
/// const VERSION: &str = read_version_from_package_json!();
/// ```
#[proc_macro]
pub fn read_version_from_package_json(_item: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[derive(serde::Deserialize)]
struct PackageJson {
version: String,
}
let file = std::fs::File::open("package.json").expect("Failed to open package.json");
let json: PackageJson = serde_json::from_reader(file).unwrap();
let version = &json.version;
quote! { #version }.into()
}
/// Export an enum to TypeScript, and derive [Clone].
///
/// You need this macro because [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
/// automatically derives the [Clone] trait for enums and causes conflicts.
///
/// This is a wrapper of [`napi_derive::napi`](https://docs.rs/napi-derive/latest/napi_derive/attr.napi.html)
/// that expands to
/// ```no_run
/// #[cfg_attr(not(feature = "napi"), derive(Clone))]
/// #[cfg_attr(feature = "napi", napi_derive::napi(attr))]
/// # enum E {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s).
#[proc_macro_attribute]
pub fn derive_clone_and_export(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
quote! {
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi(#attr))]
#item
}
.into()
}
/// Export a function, struct, enum, const, etc. to TypeScript.
///
/// This is a wrapper of [macro@napi] that expands to
/// ```no_run
/// #[cfg_attr(feature = "napi", macro_rs::napi(attr))]
/// # fn f() {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s). See [macro@napi] for more details.
#[proc_macro_attribute]
pub fn export(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
quote! {
#[cfg_attr(feature = "napi", macro_rs::napi(#attr))]
#item
}
.into()
}
/// Export a function, struct, enum, const, etc. to TypeScript
/// and make it unable to use in Rust.
///
/// This is a wrapper of [macro@napi] that expands to
/// ```no_run
/// #[cfg(feature = "napi")]
/// #[macro_rs::napi(attr)]
/// # fn f() {} // to work around doc test compilation error
/// ```
/// where `attr` is given attribute(s). See [macro@napi] for more details.
#[proc_macro_attribute]
pub fn ts_export(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
quote! {
#[cfg(feature = "napi")]
#[macro_rs::napi(#attr)]
#item
}
.into()
}
/// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
///
/// The macro is simply converted into `napi_derive::napi(...)`
/// if it is not applied to a function.
///
/// The macro sets the following attributes by default if not specified:
/// - `use_nullable = true` (if `object` or `constructor` attribute is specified)
/// - `js_name` to the camelCase version of the original function name (for functions)
///
/// The types of the function arguments is converted with following rules:
/// - `&str` and `&mut str` are converted to [`String`]
/// - `&[T]` and `&mut [T]` are converted to [`Vec<T>`]
/// - `&T` and `&mut T` are converted to `T`
/// - Other `T` remains `T`
///
/// In addition, return type [`Result<T>`] and [`Result<T, E>`] are converted to [`napi::Result<T>`](https://docs.rs/napi/latest/napi/type.Result.html).
/// Note that `E` must implement [`std::string::ToString`] trait.
///
/// # Examples
/// ## Applying the macro to a struct
/// ```
/// #[macro_rs::napi(object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
/// ```
/// simply becomes
/// ```
/// #[napi_derive::napi(use_nullable = true, object)]
/// struct Person {
/// id: i32,
/// name: String,
/// }
/// ```
///
/// ## Function with explicitly specified `js_name`
/// ```
/// #[macro_rs::napi(js_name = "add1")]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// ```
/// generates
/// ```
/// # pub fn add_one(x: i32) -> i32 {
/// # x + 1
/// # }
/// #[napi_derive::napi(js_name = "add1",)]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// ```
///
/// ## Function with `i32` argument
/// ```
/// #[macro_rs::napi]
/// pub fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// ```
/// generates
/// ```
/// # pub fn add_one(x: i32) -> i32 {
/// # x + 1
/// # }
/// #[napi_derive::napi(js_name = "addOne",)]
/// pub fn add_one_napi(x: i32) -> i32 {
/// add_one(x)
/// }
/// ```
///
/// ## Function with `&str` argument
/// ```
/// #[macro_rs::napi]
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
/// str1.to_owned() + str2
/// }
/// ```
/// generates
/// ```
/// # pub fn concatenate_string(str1: &str, str2: &str) -> String {
/// # str1.to_owned() + str2
/// # }
/// #[napi_derive::napi(js_name = "concatenateString",)]
/// pub fn concatenate_string_napi(str1: String, str2: String) -> String {
/// concatenate_string(&str1, &str2)
/// }
/// ```
///
/// ## Function with `&[String]` argument
/// ```
/// #[macro_rs::napi]
/// pub fn string_array_length(array: &[String]) -> u32 {
/// array.len() as u32
/// }
/// ```
/// generates
/// ```
/// # pub fn string_array_length(array: &[String]) -> u32 {
/// # array.len() as u32
/// # }
/// #[napi_derive::napi(js_name = "stringArrayLength",)]
/// pub fn string_array_length_napi(array: Vec<String>) -> u32 {
/// string_array_length(&array)
/// }
/// ```
///
/// ## Function with `Result<T, E>` return type
/// ```ignore
/// #[derive(thiserror::Error, Debug)]
/// pub enum IntegerDivisionError {
/// #[error("Divided by zero")]
/// DividedByZero,
/// #[error("Not divisible with remainder = {0}")]
/// NotDivisible(i64),
/// }
///
/// #[macro_rs::napi]
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
/// match divisor {
/// 0 => Err(IntegerDivisionError::DividedByZero),
/// _ => match dividend % divisor {
/// 0 => Ok(dividend / divisor),
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
/// },
/// }
/// }
/// ```
/// generates
/// ```ignore
/// # #[derive(thiserror::Error, Debug)]
/// # pub enum IntegerDivisionError {
/// # #[error("Divided by zero")]
/// # DividedByZero,
/// # #[error("Not divisible with remainder = {0}")]
/// # NotDivisible(i64),
/// # }
/// # pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
/// # match divisor {
/// # 0 => Err(IntegerDivisionError::DividedByZero),
/// # _ => match dividend % divisor {
/// # 0 => Ok(dividend / divisor),
/// # remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
/// # },
/// # }
/// # }
/// #[napi_derive::napi(js_name = "integerDivide",)]
/// pub fn integer_divide_napi(dividend: i64, divisor: i64) -> napi::Result<i64> {
/// integer_divide(dividend, divisor).map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
/// }
/// ```
#[proc_macro_attribute]
pub fn napi(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
napi_impl(attr.into(), item.into()).into()
}
fn napi_impl(macro_attr: TokenStream, item: TokenStream) -> TokenStream {
let macro_attr_tokens: Vec<TokenTree> = macro_attr.clone().into_iter().collect();
// generated extra macro attr TokenStream (prepended before original input `macro_attr`)
let mut extra_macro_attr = TokenStream::new();
let item: syn::Item =
syn::parse2(item).expect("Failed to parse input TokenStream to syn::Item");
// handle non-functions
let syn::Item::Fn(item_fn) = item else {
// append `use_nullable = true` if `object` or `constructor` present but not `use_nullable`
if macro_attr_tokens.iter().any(|token| {
matches!(token, TokenTree::Ident(ident) if ident == "object" || ident == "constructor")
}) && !macro_attr_tokens.iter().any(|token| {
matches!(token, TokenTree::Ident(ident) if ident == "use_nullable")
}) {
quote! { use_nullable = true, }.to_tokens(&mut extra_macro_attr);
}
return quote! {
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
#item
};
};
// handle functions
let ident = &item_fn.sig.ident;
let item_fn_attrs = &item_fn.attrs;
let item_fn_vis = &item_fn.vis;
let mut item_fn_sig = item_fn.sig.clone();
let mut function_call_modifiers = Vec::<TokenStream>::new();
// append "_napi" to function name
item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap();
// append `.await` to function call in async function
if item_fn_sig.asyncness.is_some() {
function_call_modifiers.push(quote! {
.await
});
}
// convert return type `...::Result<T, ...>` to `napi::Result<T>`
if let syn::ReturnType::Type(_, ref mut return_type) = item_fn_sig.output {
if let Some(result_generic_type) = (|| {
let syn::Type::Path(return_type_path) = &**return_type else {
return None;
};
// match a::b::c::Result
let last_segment = return_type_path.path.segments.last()?;
if last_segment.ident != "Result" {
return None;
};
// extract <T, ...> from Result<T, ...>
let syn::PathArguments::AngleBracketed(generic_arguments) = &last_segment.arguments
else {
return None;
};
// return T only
generic_arguments.args.first()
})() {
// modify return type
*return_type = syn::parse_quote! {
napi::Result<#result_generic_type>
};
// add modifier to function call result
function_call_modifiers.push(quote! {
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
});
}
};
// arguments in function call
let called_args: Vec<TokenStream> = item_fn_sig
.inputs
.iter_mut()
.map(|input| match input {
// self
syn::FnArg::Receiver(arg) => {
let mut tokens = TokenStream::new();
if let Some((ampersand, lifetime)) = &arg.reference {
ampersand.to_tokens(&mut tokens);
lifetime.to_tokens(&mut tokens);
}
arg.mutability.to_tokens(&mut tokens);
arg.self_token.to_tokens(&mut tokens);
tokens
}
// typed argument
syn::FnArg::Typed(arg) => {
match &mut *arg.pat {
syn::Pat::Ident(ident) => {
let name = &ident.ident;
match &*arg.ty {
// reference type argument => move ref from sigature to function call
syn::Type::Reference(r) => {
// add reference anotations to arguments in function call
let mut tokens = TokenStream::new();
r.and_token.to_tokens(&mut tokens);
if let Some(lifetime) = &r.lifetime {
lifetime.to_tokens(&mut tokens);
}
r.mutability.to_tokens(&mut tokens);
name.to_tokens(&mut tokens);
// modify napi argument types in function sigature
// (1) add `mut` token to `&mut` type
ident.mutability = r.mutability;
// (2) remove reference
*arg.ty = syn::Type::Verbatim(match &*r.elem {
syn::Type::Slice(slice) => {
let ty = &*slice.elem;
quote! { Vec<#ty> }
}
_ => {
let elem_tokens = r.elem.to_token_stream();
match elem_tokens.to_string().as_str() {
// &str => String
"str" => quote! { String },
// &T => T
_ => elem_tokens,
}
}
});
// return arguments in function call
tokens
}
// o.w., return it as is
_ => quote! { #name },
}
}
pat => panic!("Unexpected FnArg: {pat:#?}"),
}
}
})
.collect();
// handle macro attr
// append js_name if not specified
if !macro_attr_tokens
.iter()
.any(|token| matches!(token, TokenTree::Ident(ident) if ident == "js_name"))
{
let js_name = ident.to_string().to_case(Case::Camel);
quote! { js_name = #js_name, }.to_tokens(&mut extra_macro_attr);
}
quote! {
#item_fn
#[napi_derive::napi(#extra_macro_attr #macro_attr)]
#(#item_fn_attrs)*
#item_fn_vis #item_fn_sig {
#ident(#(#called_args),*)
#(#function_call_modifiers)*
}
}
}
#[cfg(test)]
mod tests {
use proc_macro2::TokenStream;
use quote::quote;
macro_rules! test_macro_becomes {
($source:expr, $generated:expr) => {
assert_eq!(
super::napi_impl(TokenStream::new(), $source).to_string(),
$generated.to_string(),
)
};
($macro_attr:expr, $source:expr, $generated:expr) => {
assert_eq!(
super::napi_impl($macro_attr, $source).to_string(),
$generated.to_string(),
)
};
}
macro_rules! test_macro_generates {
($source:expr, $generated:expr) => {
assert_eq!(
super::napi_impl(TokenStream::new(), $source).to_string(),
format!("{} {}", $source, $generated),
)
};
($macro_attr:expr, $source:expr, $generated:expr) => {
assert_eq!(
super::napi_impl($macro_attr, $source).to_string(),
format!("{} {}", $source, $generated),
)
};
}
#[test]
fn primitive_argument() {
test_macro_generates!(
quote! {
pub fn add_one(x: i32) -> i32 {
x + 1
}
},
quote! {
#[napi_derive::napi(js_name = "addOne", )]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
);
}
#[test]
fn str_ref_argument() {
test_macro_generates!(
quote! {
pub fn concatenate_string(str1: &str, str2: &str) -> String {
str1.to_owned() + str2
}
},
quote! {
#[napi_derive::napi(js_name = "concatenateString", )]
pub fn concatenate_string_napi(str1: String, str2: String) -> String {
concatenate_string(&str1, &str2)
}
}
);
}
#[test]
fn mut_ref_argument() {
test_macro_generates!(
quote! {
pub fn append_string_and_clone(
base_str: &mut String,
appended_str: &str,
) -> String {
base_str.push_str(appended_str);
base_str.to_owned()
}
},
quote! {
#[napi_derive::napi(js_name = "appendStringAndClone", )]
pub fn append_string_and_clone_napi(
mut base_str: String,
appended_str: String,
) -> String {
append_string_and_clone(&mut base_str, &appended_str)
}
}
);
}
#[test]
fn result_return_type() {
test_macro_generates!(
quote! {
pub fn integer_divide(
dividend: i64,
divisor: i64,
) -> Result<i64, IntegerDivisionError> {
match divisor {
0 => Err(IntegerDivisionError::DividedByZero),
_ => match dividend % divisor {
0 => Ok(dividend / divisor),
remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
},
}
}
},
quote! {
#[napi_derive::napi(js_name = "integerDivide", )]
pub fn integer_divide_napi(
dividend: i64,
divisor: i64,
) -> napi::Result<i64> {
integer_divide(dividend, divisor)
.map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
}
}
);
}
#[test]
fn async_function() {
test_macro_generates!(
quote! {
pub async fn async_add_one(x: i32) -> i32 {
x + 1
}
},
quote! {
#[napi_derive::napi(js_name = "asyncAddOne", )]
pub async fn async_add_one_napi(x: i32) -> i32 {
async_add_one(x)
.await
}
}
)
}
#[test]
fn slice_type() {
test_macro_generates!(
quote! {
pub fn string_array_length(array: &[String]) -> u32 {
array.len() as u32
}
},
quote! {
#[napi_derive::napi(js_name = "stringArrayLength", )]
pub fn string_array_length_napi(array: Vec<String>) -> u32 {
string_array_length(&array)
}
}
)
}
#[test]
fn object() {
test_macro_becomes!(
quote! { object },
quote! {
struct Person {
id: i32,
name: Option<String>,
}
},
quote! {
#[napi_derive::napi(use_nullable = true, object)]
struct Person {
id: i32,
name: Option<String>,
}
}
)
}
#[test]
fn object_with_explicitly_set_use_nullable() {
test_macro_becomes!(
quote! { object, use_nullable = false },
quote! {
struct Person {
id: i32,
name: Option<String>,
}
},
quote! {
#[napi_derive::napi(object, use_nullable = false)]
struct Person {
id: i32,
name: Option<String>,
}
}
)
}
#[test]
fn macro_attr() {
test_macro_generates!(
quote! {
ts_return_type = "number"
},
quote! {
pub fn add_one(x: i32) -> i32 {
x + 1
}
},
quote! {
#[napi_derive::napi(js_name = "addOne", ts_return_type = "number")]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
)
}
#[test]
fn explicitly_specified_js_name() {
test_macro_generates!(
quote! {
js_name = "add1"
},
quote! {
pub fn add_one(x: i32) -> i32 {
x + 1
}
},
quote! {
#[napi_derive::napi(js_name = "add1")]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
)
}
#[test]
fn explicitly_specified_js_name_and_other_macro_attr() {
test_macro_generates!(
quote! { ts_return_type = "number", js_name = "add1" },
quote! {
pub fn add_one(x: i32) -> i32 {
x + 1
}
},
quote! {
#[napi_derive::napi(ts_return_type = "number", js_name = "add1")]
pub fn add_one_napi(x: i32) -> i32 {
add_one(x)
}
}
)
}
}

View file

@ -70,8 +70,8 @@ importers:
specifier: 1.7.0
version: 1.7.0
'@redocly/openapi-core':
specifier: 1.14.0
version: 1.14.0
specifier: 1.15.0
version: 1.15.0
'@sinonjs/fake-timers':
specifier: 11.2.2
version: 11.2.2
@ -91,8 +91,8 @@ importers:
specifier: 0.5.0
version: 0.5.0
aws-sdk:
specifier: 2.1638.0
version: 2.1638.0
specifier: 2.1639.0
version: 2.1639.0
axios:
specifier: 1.7.2
version: 1.7.2
@ -103,8 +103,8 @@ importers:
specifier: 2.0.5
version: 2.0.5
bull:
specifier: 4.12.9
version: 4.12.9
specifier: 4.13.0
version: 4.13.0
cacheable-lookup:
specifier: git+https://github.com/TheEssem/cacheable-lookup.git#dd2fb616366a3c68dcf321a57a67295967b204bf
version: https://codeload.github.com/TheEssem/cacheable-lookup/tar.gz/dd2fb616366a3c68dcf321a57a67295967b204bf
@ -320,7 +320,7 @@ importers:
version: 0.2.3
typeorm:
specifier: 0.3.20
version: 0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
version: 0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
ulid:
specifier: 2.3.0
version: 2.3.0
@ -343,10 +343,10 @@ importers:
devDependencies:
'@swc/cli':
specifier: 0.3.12
version: 0.3.12(@swc/core@1.5.27)(chokidar@3.6.0)
version: 0.3.12(@swc/core@1.5.28)(chokidar@3.6.0)
'@swc/core':
specifier: 1.5.27
version: 1.5.27
specifier: 1.5.28
version: 1.5.28
'@types/adm-zip':
specifier: 0.5.5
version: 0.5.5
@ -496,13 +496,13 @@ importers:
version: 2.0.0
swc-loader:
specifier: 0.2.6
version: 0.2.6(@swc/core@1.5.27)(webpack@5.91.0(@swc/core@1.5.27))
version: 0.2.6(@swc/core@1.5.28)(webpack@5.92.0(@swc/core@1.5.28))
ts-loader:
specifier: 9.5.1
version: 9.5.1(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.5.27))
version: 9.5.1(typescript@5.4.5)(webpack@5.92.0(@swc/core@1.5.28))
ts-node:
specifier: 10.9.2
version: 10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
version: 10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
tsconfig-paths:
specifier: 4.2.0
version: 4.2.0
@ -513,8 +513,8 @@ importers:
specifier: 5.4.5
version: 5.4.5
webpack:
specifier: 5.91.0
version: 5.91.0(@swc/core@1.5.27)
specifier: 5.92.0
version: 5.92.0(@swc/core@1.5.28)
ws:
specifier: 8.17.0
version: 8.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
@ -592,7 +592,7 @@ importers:
version: 9.0.8
'@vitejs/plugin-vue':
specifier: 5.0.5
version: 5.0.5(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))
version: 5.0.5(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))
'@vue/runtime-core':
specifier: 3.4.27
version: 3.4.27
@ -708,8 +708,8 @@ importers:
specifier: 1.1.2
version: 1.1.2
sass:
specifier: 1.77.4
version: 1.77.4
specifier: 1.77.5
version: 1.77.5
seedrandom:
specifier: 3.0.5
version: 3.0.5
@ -745,10 +745,10 @@ importers:
version: 10.0.0
vite:
specifier: 5.2.13
version: 5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)
version: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0)
vite-plugin-compression:
specifier: 0.5.1
version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0))
version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0))
vue:
specifier: 3.4.27
version: 3.4.27(typescript@5.4.5)
@ -780,10 +780,10 @@ importers:
devDependencies:
'@swc/cli':
specifier: 0.3.12
version: 0.3.12(@swc/core@1.5.27)(chokidar@3.6.0)
version: 0.3.12(@swc/core@1.5.28)(chokidar@3.6.0)
'@swc/core':
specifier: 1.5.27
version: 1.5.27
specifier: 1.5.28
version: 1.5.28
'@swc/types':
specifier: 0.1.8
version: 0.1.8
@ -795,7 +795,7 @@ importers:
version: 20.14.2
jest:
specifier: 29.7.0
version: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
version: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-fetch-mock:
specifier: 3.0.3
version: 3.0.3
@ -807,10 +807,10 @@ importers:
version: 9.3.1
ts-jest:
specifier: 29.1.4
version: 29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)))(typescript@5.4.5)
version: 29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)))(typescript@5.4.5)
ts-node:
specifier: 10.9.2
version: 10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
version: 10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
tsd:
specifier: 0.31.0
version: 0.31.0
@ -828,10 +828,10 @@ importers:
version: 6.2.1
vite:
specifier: 5.2.13
version: 5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)
version: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0)
vite-plugin-compression:
specifier: 0.5.1
version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0))
version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0))
packages:
@ -1887,11 +1887,11 @@ packages:
'@redocly/ajv@8.11.0':
resolution: {integrity: sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==}
'@redocly/config@0.5.0':
resolution: {integrity: sha512-oA1ezWPT2tSV9CLk0FtZlViaFKtp+id3iAVeKBme1DdP4xUCdxEdP8umB21iLKdc6leRd5uGa+T5Ox4nHBAXWg==}
'@redocly/config@0.6.0':
resolution: {integrity: sha512-hNVN3eTxFj2nHYX0gGzZxxXwdE0DXWeWou1TIK3HYf0S9VKVxTxjO9EZbMB7iVUqaHkeqy4PSjlBQcEgD0Ftjg==}
'@redocly/openapi-core@1.14.0':
resolution: {integrity: sha512-sraF4PGVcc6t6CaYw5raO/GWeOaa6UjcEvH/+Qm7zp+q/fbWAMwbj+1QzaNvpMspCwF+xW6TddDcnXrCDmqYVA==}
'@redocly/openapi-core@1.15.0':
resolution: {integrity: sha512-ac+3nn9y/dE+cgIVgIdq7eIisjZlBEJptLsCbOVLIsR2jb+O1SznXeyqy2MkTHMSs6zM/KHP4bMQy0DGmi7K0Q==}
engines: {node: '>=14.19.0', npm: '>=7.0.0'}
'@rollup/plugin-alias@5.1.0':
@ -2048,68 +2048,68 @@ packages:
cpu: [arm64]
os: [android]
'@swc/core-darwin-arm64@1.5.27':
resolution: {integrity: sha512-jyoygXBcUcwUya2BI7Uvl0jwcm4kd0RBDGGkWgcFAZmwucSuLT3EsbpWhOwlL3ACT4rpnRlvh+k8nJlq3+w2Aw==}
'@swc/core-darwin-arm64@1.5.28':
resolution: {integrity: sha512-sP6g63ybzIdOWNDbn51tyHN8EMt7Mb4RMeHQEsXB7wQfDvzhpWB+AbfK6Gs3Q8fwP/pmWIrWW9csKOc1K2Mmkg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.5.27':
resolution: {integrity: sha512-eOC583D6b3MS9oODCcZUvAV7ajunjENAPVQL7aZaW+piERW+o4koZAiPlzFdMAUMj7UeVg+UN9sBBbTbJgruIA==}
'@swc/core-darwin-x64@1.5.28':
resolution: {integrity: sha512-Bd/agp/g7QocQG5AuorOzSC78t8OzeN+pCN/QvJj1CvPhvppjJw6e1vAbOR8vO2vvGi2pvtf3polrYQStJtSiA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.5.27':
resolution: {integrity: sha512-bMvX0yF7WYzn1K+s0JWJhvyA3OeZHVrdjka8eZ4LSeuLfC0ggJefo+klyeuN2szn/LYP6/3oByyrWNY8RSHB4w==}
'@swc/core-linux-arm-gnueabihf@1.5.28':
resolution: {integrity: sha512-Wr3TwPGIveS9/OBWm0r9VAL8wkCR0zQn46J8K01uYCmVhUNK3Muxjs0vQBZaOrGu94mqbj9OXY+gB3W7aDvGdA==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.5.27':
resolution: {integrity: sha512-KlkOcSPxrCqZTm4XrT/LT1o9gmyM2T6bw/hL6IwTYRBJg+sej4rc9iSfVRFZBfNuG3EVkFQSXxik+0yVOXR93Q==}
'@swc/core-linux-arm64-gnu@1.5.28':
resolution: {integrity: sha512-8G1ZwVTuLgTAVTMPD+M97eU6WeiRIlGHwKZ5fiJHPBcz1xqIC7jQcEh7XBkobkYoU5OILotls3gzjRt8CMNyDQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.5.27':
resolution: {integrity: sha512-EwdTt5qykxFXJu7kS+0X0Mp/IlwO8KJ6LVNak2+N8bt1J1q/nCdg1tRDOYQ1Z/MVa1Tm+lJ664Qs1y2NY2SDTw==}
'@swc/core-linux-arm64-musl@1.5.28':
resolution: {integrity: sha512-0Ajdzb5Fzvz+XUbN5ESeHAz9aHHSYiQcm+vmsDi0TtPHmsalfnqEPZmnK0zPALPJPLQP2dDo4hELeDg3/c3xgA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.5.27':
resolution: {integrity: sha512-RsBbxbiSNWLJ2jbAdITtv30J4eZw4O/JJ5zxYgWI54TdY7YrVsqIdzuX+ldximt+CYvO9irHm/mSr/IJY2YXrw==}
'@swc/core-linux-x64-gnu@1.5.28':
resolution: {integrity: sha512-ueQ9VejnQUM2Pt+vT0IAKoF4vYBWUP6n1KHGdILpoGe3LuafQrqu7RoyQ15C7/AYii7hAeNhTFdf6gLbg8cjFg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.5.27':
resolution: {integrity: sha512-XpRx0Kpy6JEi1WSMqUfR3k8hXLqNOkVqFcUfzvfQ4QNBX5Ek7ywh7WAxlPhCrFp+wAfNAqqUyRY1xZpLvRU51A==}
'@swc/core-linux-x64-musl@1.5.28':
resolution: {integrity: sha512-G5th8Mg0az8CbY4GQt9/m5hg2Y0kGIwvQBeVACuLQB6q2Y4txzdiTpjmFqUUhEvvl7Klyx1IHvNhfXs3zpt7PA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.5.27':
resolution: {integrity: sha512-pwSTUIokyIp+Ha1pur34qdYjxqL1QzhP/HM8anzsFs4yvV2LSI7c3qc4GWPNv2eQ9WiFXyo29uCEIRN6qig7wg==}
'@swc/core-win32-arm64-msvc@1.5.28':
resolution: {integrity: sha512-JezwCGavZ7CkNXx4yInI4kpb71L0zxzxA9BFlmnsGKEEjVQcKc3hFpmIzfFVs+eotlBUwDNb0+Yo9m6Cb7lllA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.5.27':
resolution: {integrity: sha512-S0S6vqFscvmxPolwmpZvTRfTbYR+eGcyc0ge4x/+HcnBCm+m84rcGxmp3bBb1edNFaIV+X47BlGvvh85cJ4rkQ==}
'@swc/core-win32-ia32-msvc@1.5.28':
resolution: {integrity: sha512-q8tW5J4RkOkl7vYShnWS//VAb2Ngolfm9WOMaF2GRJUr2Y/Xeb/+cNjdsNOqea2BzW049D5vdP7XPmir3/zUZw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.5.27':
resolution: {integrity: sha512-aem+BcNW42JPbvV6L3Jl3LLj6G80aYADzYenToYisy0Aop0XZAxL/0FbhV+xWORNFtIUKynNtaa1CK7w0UxehQ==}
'@swc/core-win32-x64-msvc@1.5.28':
resolution: {integrity: sha512-jap6EiB3wG1YE1hyhNr9KLPpH4PGm+5tVMfN0l7fgKtV0ikgpcEN/YF94tru+z5m2HovqYW009+Evq9dcVGmpg==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.5.27':
resolution: {integrity: sha512-HmSSCBoUSRDFAd8aEB+WILkCofIp1c2OU6ZJWu1aCt6pijwQSkA4y51CTBcdvyy/+zX1W3cic7alfdhmQxxeEQ==}
'@swc/core@1.5.28':
resolution: {integrity: sha512-muCdNIqOTURUgYeyyOLYE3ShL8SZO6dw6bhRm6dCvxWzCZOncPc5fB0kjcPXTML+9KJoHL7ks5xg+vsQK+v6ig==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '*'
@ -2698,8 +2698,8 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
acorn-import-assertions@1.9.0:
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies:
acorn: ^8
@ -2887,8 +2887,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
aws-sdk@2.1638.0:
resolution: {integrity: sha512-/Li+eOMvJOLuYXimt3YPd6ec9Xvzh6L5KLfU5bjuJrltQqBcW7paL+PnFqSjm7zef+fPJT7h+8sqEcuRaGUmRA==}
aws-sdk@2.1639.0:
resolution: {integrity: sha512-3vi9ONXhROfXTjsulFurKtJ/vBjiXirhwrRY6C7QRJyI/+m9lphtBivSYynnu7q2saAqC9ArlkEWQLRFUPy+Zg==}
engines: {node: '>= 10.0.0'}
axios@0.24.0:
@ -3045,8 +3045,8 @@ packages:
builtins@5.1.0:
resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==}
bull@4.12.9:
resolution: {integrity: sha512-rqka/O9ZBfrKgI4fanhN6XW0AJ9WYRakjHlCJPjoHyh79xIvEjyU8hvs/CCeRdrbU6zSw8UNfDOjCUaQO1MTuQ==}
bull@4.13.0:
resolution: {integrity: sha512-XAAhHeXtW+luEYuid3shGv25ErmsegrVXMukbP7AqNUgRgiTnRnIGdjPyzmHu3/Tjm6jNdODqLHJ12t07fyUrQ==}
engines: {node: '>=12'}
busboy@1.6.0:
@ -3878,6 +3878,10 @@ packages:
resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==}
engines: {node: '>=10.13.0'}
enhanced-resolve@5.17.0:
resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==}
engines: {node: '>=10.13.0'}
entities@1.1.2:
resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==}
@ -6649,8 +6653,8 @@ packages:
sanitize-html@2.13.0:
resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==}
sass@1.77.4:
resolution: {integrity: sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==}
sass@1.77.5:
resolution: {integrity: sha512-oDfX1mukIlxacPdQqNb6mV2tVCrnE+P3nVYioy72V5tlk56CPNcO4TCuFcaCRKKfJ1M3lH95CleRS+dVKL2qMg==}
engines: {node: '>=14.0.0'}
hasBin: true
@ -7566,8 +7570,8 @@ packages:
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
engines: {node: '>=10.13.0'}
webpack@5.91.0:
resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}
webpack@5.92.0:
resolution: {integrity: sha512-Bsw2X39MYIgxouNATyVpCNVWBCuUwDgWtN78g6lSdPJRLaQ/PUVm/oXcaRAyY/sMFoKFQrsPeqvTizWtq7QPCA==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
@ -8595,7 +8599,7 @@ snapshots:
jest-util: 29.7.0
slash: 3.0.0
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))':
'@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))':
dependencies:
'@jest/console': 29.7.0
'@jest/reporters': 29.7.0
@ -8609,7 +8613,7 @@ snapshots:
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@ -8949,12 +8953,12 @@ snapshots:
require-from-string: 2.0.2
uri-js: 4.4.1
'@redocly/config@0.5.0': {}
'@redocly/config@0.6.0': {}
'@redocly/openapi-core@1.14.0':
'@redocly/openapi-core@1.15.0':
dependencies:
'@redocly/ajv': 8.11.0
'@redocly/config': 0.5.0
'@redocly/config': 0.6.0
colorette: 1.4.0
js-levenshtein: 1.1.6
js-yaml: 4.1.0
@ -9058,10 +9062,10 @@ snapshots:
'@sqltools/formatter@1.2.5': {}
'@swc/cli@0.3.12(@swc/core@1.5.27)(chokidar@3.6.0)':
'@swc/cli@0.3.12(@swc/core@1.5.28)(chokidar@3.6.0)':
dependencies:
'@mole-inc/bin-wrapper': 8.0.1
'@swc/core': 1.5.27
'@swc/core': 1.5.28
'@swc/counter': 0.1.3
commander: 8.3.0
fast-glob: 3.3.2
@ -9078,51 +9082,51 @@ snapshots:
'@swc/wasm': 1.2.130
optional: true
'@swc/core-darwin-arm64@1.5.27':
'@swc/core-darwin-arm64@1.5.28':
optional: true
'@swc/core-darwin-x64@1.5.27':
'@swc/core-darwin-x64@1.5.28':
optional: true
'@swc/core-linux-arm-gnueabihf@1.5.27':
'@swc/core-linux-arm-gnueabihf@1.5.28':
optional: true
'@swc/core-linux-arm64-gnu@1.5.27':
'@swc/core-linux-arm64-gnu@1.5.28':
optional: true
'@swc/core-linux-arm64-musl@1.5.27':
'@swc/core-linux-arm64-musl@1.5.28':
optional: true
'@swc/core-linux-x64-gnu@1.5.27':
'@swc/core-linux-x64-gnu@1.5.28':
optional: true
'@swc/core-linux-x64-musl@1.5.27':
'@swc/core-linux-x64-musl@1.5.28':
optional: true
'@swc/core-win32-arm64-msvc@1.5.27':
'@swc/core-win32-arm64-msvc@1.5.28':
optional: true
'@swc/core-win32-ia32-msvc@1.5.27':
'@swc/core-win32-ia32-msvc@1.5.28':
optional: true
'@swc/core-win32-x64-msvc@1.5.27':
'@swc/core-win32-x64-msvc@1.5.28':
optional: true
'@swc/core@1.5.27':
'@swc/core@1.5.28':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.8
optionalDependencies:
'@swc/core-darwin-arm64': 1.5.27
'@swc/core-darwin-x64': 1.5.27
'@swc/core-linux-arm-gnueabihf': 1.5.27
'@swc/core-linux-arm64-gnu': 1.5.27
'@swc/core-linux-arm64-musl': 1.5.27
'@swc/core-linux-x64-gnu': 1.5.27
'@swc/core-linux-x64-musl': 1.5.27
'@swc/core-win32-arm64-msvc': 1.5.27
'@swc/core-win32-ia32-msvc': 1.5.27
'@swc/core-win32-x64-msvc': 1.5.27
'@swc/core-darwin-arm64': 1.5.28
'@swc/core-darwin-x64': 1.5.28
'@swc/core-linux-arm-gnueabihf': 1.5.28
'@swc/core-linux-arm64-gnu': 1.5.28
'@swc/core-linux-arm64-musl': 1.5.28
'@swc/core-linux-x64-gnu': 1.5.28
'@swc/core-linux-x64-musl': 1.5.28
'@swc/core-win32-arm64-msvc': 1.5.28
'@swc/core-win32-ia32-msvc': 1.5.28
'@swc/core-win32-x64-msvc': 1.5.28
'@swc/counter@0.1.3': {}
@ -9679,9 +9683,9 @@ snapshots:
'@typescript-eslint/types': 6.21.0
eslint-visitor-keys: 3.4.3
'@vitejs/plugin-vue@5.0.5(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))':
'@vitejs/plugin-vue@5.0.5(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0))(vue@3.4.27(typescript@5.4.5))':
dependencies:
vite: 5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)
vite: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0)
vue: 3.4.27(typescript@5.4.5)
'@volar/language-core@2.3.0':
@ -9862,7 +9866,7 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
acorn-import-assertions@1.9.0(acorn@8.11.3):
acorn-import-attributes@1.9.5(acorn@8.11.3):
dependencies:
acorn: 8.11.3
@ -10051,7 +10055,7 @@ snapshots:
dependencies:
possible-typed-array-names: 1.0.0
aws-sdk@2.1638.0:
aws-sdk@2.1639.0:
dependencies:
buffer: 4.9.2
events: 1.1.1
@ -10243,7 +10247,7 @@ snapshots:
buffer@4.9.2:
dependencies:
base64-js: 1.5.1
ieee754: 1.1.13
ieee754: 1.2.1
isarray: 1.0.0
buffer@5.7.1:
@ -10266,7 +10270,7 @@ snapshots:
dependencies:
semver: 7.6.2
bull@4.12.9:
bull@4.13.0:
dependencies:
cron-parser: 4.9.0
get-port: 5.1.1
@ -10634,13 +10638,13 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.5.2
create-jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
create-jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@ -10996,6 +11000,11 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.2.1
enhanced-resolve@5.17.0:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
entities@1.1.2: {}
entities@2.2.0: {}
@ -12474,16 +12483,16 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-cli@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
jest-cli@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
create-jest: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
exit: 0.1.2
import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-config: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@ -12493,7 +12502,7 @@ snapshots:
- supports-color
- ts-node
jest-config@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
jest-config@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@babel/core': 7.24.6
'@jest/test-sequencer': 29.7.0
@ -12519,7 +12528,7 @@ snapshots:
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 20.14.2
ts-node: 10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
ts-node: 10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@ -12757,12 +12766,12 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
'@jest/types': 29.6.3
import-local: 3.1.0
jest-cli: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-cli: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -14347,7 +14356,7 @@ snapshots:
parse-srcset: 1.0.2
postcss: 8.4.38
sass@1.77.4:
sass@1.77.5:
dependencies:
chokidar: 3.6.0
immutable: 4.3.6
@ -14711,11 +14720,11 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
swc-loader@0.2.6(@swc/core@1.5.27)(webpack@5.91.0(@swc/core@1.5.27)):
swc-loader@0.2.6(@swc/core@1.5.28)(webpack@5.92.0(@swc/core@1.5.28)):
dependencies:
'@swc/core': 1.5.27
'@swc/core': 1.5.28
'@swc/counter': 0.1.3
webpack: 5.91.0(@swc/core@1.5.27)
webpack: 5.92.0(@swc/core@1.5.28)
swiper@11.1.4: {}
@ -14756,16 +14765,16 @@ snapshots:
fast-fifo: 1.3.2
streamx: 2.16.1
terser-webpack-plugin@5.3.10(@swc/core@1.5.27)(webpack@5.91.0(@swc/core@1.5.27)):
terser-webpack-plugin@5.3.10(@swc/core@1.5.28)(webpack@5.92.0(@swc/core@1.5.28)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.31.0
webpack: 5.91.0(@swc/core@1.5.27)
webpack: 5.92.0(@swc/core@1.5.28)
optionalDependencies:
'@swc/core': 1.5.27
'@swc/core': 1.5.28
terser@5.31.0:
dependencies:
@ -14868,11 +14877,11 @@ snapshots:
dependencies:
typescript: 5.4.5
ts-jest@29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)))(typescript@5.4.5):
ts-jest@29.1.4(@babel/core@7.24.6)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.6))(jest@29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)))(typescript@5.4.5):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest: 29.7.0(@types/node@20.14.2)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5))
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
@ -14886,7 +14895,7 @@ snapshots:
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.24.6)
ts-loader@9.5.1(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.5.27)):
ts-loader@9.5.1(typescript@5.4.5)(webpack@5.92.0(@swc/core@1.5.28)):
dependencies:
chalk: 4.1.2
enhanced-resolve: 5.16.1
@ -14894,9 +14903,9 @@ snapshots:
semver: 7.6.2
source-map: 0.7.4
typescript: 5.4.5
webpack: 5.91.0(@swc/core@1.5.27)
webpack: 5.92.0(@swc/core@1.5.28)
ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5):
ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@ -14914,7 +14923,7 @@ snapshots:
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
optionalDependencies:
'@swc/core': 1.5.27
'@swc/core': 1.5.28
'@swc/wasm': 1.2.130
tsconfig-paths@3.15.0:
@ -15016,7 +15025,7 @@ snapshots:
typedarray@0.0.6: {}
typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@sqltools/formatter': 1.2.5
app-root-path: 3.1.0
@ -15036,7 +15045,7 @@ snapshots:
optionalDependencies:
ioredis: 5.4.1
pg: 8.12.0
ts-node: 10.9.2(@swc/core@1.5.27)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
ts-node: 10.9.2(@swc/core@1.5.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
transitivePeerDependencies:
- supports-color
@ -15156,16 +15165,16 @@ snapshots:
core-util-is: 1.0.2
extsprintf: 1.3.0
vite-plugin-compression@0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)):
vite-plugin-compression@0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0)):
dependencies:
chalk: 4.1.2
debug: 4.3.4(supports-color@8.1.1)
fs-extra: 10.1.0
vite: 5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0)
vite: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0)
transitivePeerDependencies:
- supports-color
vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.0):
vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.0):
dependencies:
esbuild: 0.20.2
postcss: 8.4.38
@ -15173,7 +15182,7 @@ snapshots:
optionalDependencies:
'@types/node': 20.14.2
fsevents: 2.3.3
sass: 1.77.4
sass: 1.77.5
stylus: 0.57.0
terser: 5.31.0
@ -15261,7 +15270,7 @@ snapshots:
webpack-sources@3.2.3: {}
webpack@5.91.0(@swc/core@1.5.27):
webpack@5.92.0(@swc/core@1.5.28):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.5
@ -15269,10 +15278,10 @@ snapshots:
'@webassemblyjs/wasm-edit': 1.12.1
'@webassemblyjs/wasm-parser': 1.12.1
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
acorn-import-attributes: 1.9.5(acorn@8.11.3)
browserslist: 4.23.1
chrome-trace-event: 1.0.4
enhanced-resolve: 5.16.1
enhanced-resolve: 5.17.0
es-module-lexer: 1.5.3
eslint-scope: 5.1.1
events: 3.3.0
@ -15284,7 +15293,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.10(@swc/core@1.5.27)(webpack@5.91.0(@swc/core@1.5.27))
terser-webpack-plugin: 5.3.10(@swc/core@1.5.28)(webpack@5.92.0(@swc/core@1.5.28))
watchpack: 2.4.1
webpack-sources: 3.2.3
transitivePeerDependencies:
@ -15403,7 +15412,7 @@ snapshots:
xml2js@0.6.2:
dependencies:
sax: 1.2.1
sax: 1.4.1
xmlbuilder: 11.0.1
xmlbuilder@11.0.1: {}