From 8657cc61a7ea6cf024bdc440eed786d2f6127864 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Tue, 11 Jun 2024 12:14:22 +0000
Subject: [PATCH 01/16] chore(deps): update dependency @swc/core to v1.5.28

---
 packages/backend/package.json     |   2 +-
 packages/firefish-js/package.json |   2 +-
 pnpm-lock.yaml                    | 176 +++++++++++++++---------------
 3 files changed, 90 insertions(+), 90 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 85944e1158..abb315b15c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -121,7 +121,7 @@
 	},
 	"devDependencies": {
 		"@swc/cli": "0.3.12",
-		"@swc/core": "1.5.27",
+		"@swc/core": "1.5.28",
 		"@types/adm-zip": "0.5.5",
 		"@types/color-convert": "2.0.3",
 		"@types/content-disposition": "0.5.8",
diff --git a/packages/firefish-js/package.json b/packages/firefish-js/package.json
index e93d232ca7..759d28c4a6 100644
--- a/packages/firefish-js/package.json
+++ b/packages/firefish-js/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 70e9bf2d3e..44cea2104b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -317,7 +317,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
@@ -337,10 +337,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
@@ -490,13 +490,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.91.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.91.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
@@ -508,7 +508,7 @@ importers:
         version: 5.4.5
       webpack:
         specifier: 5.91.0
-        version: 5.91.0(@swc/core@1.5.27)
+        version: 5.91.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)
@@ -774,10 +774,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
@@ -789,7 +789,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
@@ -801,10 +801,10 @@ importers:
         version: 9.3.1
       ts-jest:
         specifier: 29.1.4
-        version: 29.1.4(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(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.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(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
@@ -2175,68 +2175,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': '*'
@@ -8962,7 +8962,7 @@ snapshots:
       - supports-color
       - ts-node
 
-  '@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
@@ -8976,7 +8976,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
@@ -9425,10 +9425,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
@@ -9445,51 +9445,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': {}
 
@@ -11119,13 +11119,13 @@ snapshots:
       - supports-color
       - ts-node
 
-  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:
@@ -13114,16 +13114,16 @@ snapshots:
       - supports-color
       - ts-node
 
-  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
@@ -13193,7 +13193,7 @@ snapshots:
       - babel-plugin-macros
       - supports-color
 
-  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.7
       '@jest/test-sequencer': 29.7.0
@@ -13219,7 +13219,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
@@ -13469,12 +13469,12 @@ snapshots:
       - supports-color
       - ts-node
 
-  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
@@ -15468,11 +15468,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.91.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.91.0(@swc/core@1.5.28)
 
   swiper@11.1.4: {}
 
@@ -15513,16 +15513,16 @@ snapshots:
       fast-fifo: 1.3.2
       streamx: 2.18.0
 
-  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.91.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.1
-      webpack: 5.91.0(@swc/core@1.5.27)
+      webpack: 5.91.0(@swc/core@1.5.28)
     optionalDependencies:
-      '@swc/core': 1.5.27
+      '@swc/core': 1.5.28
 
   terser@5.31.1:
     dependencies:
@@ -15647,11 +15647,11 @@ snapshots:
       '@jest/types': 29.6.3
       babel-jest: 29.7.0(@babel/core@7.24.7)
 
-  ts-jest@29.1.4(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(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.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(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
@@ -15665,7 +15665,7 @@ snapshots:
       '@jest/types': 29.6.3
       babel-jest: 29.7.0(@babel/core@7.24.7)
 
-  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.91.0(@swc/core@1.5.28)):
     dependencies:
       chalk: 4.1.2
       enhanced-resolve: 5.17.0
@@ -15673,9 +15673,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.91.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
@@ -15693,7 +15693,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:
@@ -15808,7 +15808,7 @@ snapshots:
       shiki: 0.14.7
       typescript: 4.9.4
 
-  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
@@ -15828,7 +15828,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
 
@@ -16048,7 +16048,7 @@ snapshots:
 
   webpack-sources@3.2.3: {}
 
-  webpack@5.91.0(@swc/core@1.5.27):
+  webpack@5.91.0(@swc/core@1.5.28):
     dependencies:
       '@types/eslint-scope': 3.7.7
       '@types/estree': 1.0.5
@@ -16071,7 +16071,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.91.0(@swc/core@1.5.28))
       watchpack: 2.4.1
       webpack-sources: 3.2.3
     transitivePeerDependencies:

From e10a18875152488300a1ed19bddd53100d28d5c9 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Tue, 11 Jun 2024 23:23:00 +0900
Subject: [PATCH 02/16] refactor (backend): port publishNotesStream to
 backend-rs

---
 packages/backend-rs/index.d.ts                  |  1 +
 packages/backend-rs/index.js                    |  3 ++-
 packages/backend-rs/src/service/stream.rs       |  1 +
 packages/backend-rs/src/service/stream/notes.rs | 13 +++++++++++++
 packages/backend/src/services/note/create.ts    |  9 +++------
 packages/backend/src/services/stream.ts         |  9 +++++----
 6 files changed, 25 insertions(+), 11 deletions(-)
 create mode 100644 packages/backend-rs/src/service/stream/notes.rs

diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 5f51bba686..e7d8bb0e76 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -1381,6 +1381,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,
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 6559eef30f..565ca43ce3 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -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, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, 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, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, publishToNotesStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
 
 module.exports.SECOND = SECOND
 module.exports.MINUTE = MINUTE
@@ -391,6 +391,7 @@ module.exports.publishToChatIndexStream = publishToChatIndexStream
 module.exports.publishToBroadcastStream = publishToBroadcastStream
 module.exports.publishToGroupChatStream = publishToGroupChatStream
 module.exports.publishToModerationStream = publishToModerationStream
+module.exports.publishToNotesStream = publishToNotesStream
 module.exports.ChatEvent = ChatEvent
 module.exports.getTimestamp = getTimestamp
 module.exports.genId = genId
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index 4a72fc7ec0..baf3a278d3 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -5,6 +5,7 @@ pub mod chat_index;
 pub mod custom_emoji;
 pub mod group_chat;
 pub mod moderation;
+pub mod notes;
 
 use crate::{
     config::CONFIG,
diff --git a/packages/backend-rs/src/service/stream/notes.rs b/packages/backend-rs/src/service/stream/notes.rs
new file mode 100644
index 0000000000..6c2336e347
--- /dev/null
+++ b/packages/backend-rs/src/service/stream/notes.rs
@@ -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;
+
+#[crate::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
+}
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index b37c007152..1dd2653845 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -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();
diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts
index 36914d4d41..c60d5ce974 100644
--- a/packages/backend/src/services/stream.ts
+++ b/packages/backend/src/services/stream.ts
@@ -193,9 +193,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>(
@@ -221,7 +222,7 @@ export const publishUserEvent = publisher.publishUserEvent;
 export const publishMainStream = publisher.publishMainStream;
 export const publishDriveStream = publisher.publishDriveStream;
 export const publishNoteStream = publisher.publishNoteStream;
-export const publishNotesStream = publisher.publishNotesStream;
+// export const publishNotesStream = publisher.publishNotesStream;
 // export const publishChannelStream = publisher.publishChannelStream;
 export const publishUserListStream = publisher.publishUserListStream;
 // export const publishAntennaStream = publisher.publishAntennaStream;

From 4e36d2216432f52d4364686935bbcf6788f40ea0 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Tue, 11 Jun 2024 23:59:01 +0900
Subject: [PATCH 03/16] refactor (backend): port publishDriveStream to
 backend-rs

---
 packages/backend-rs/index.d.ts                | 12 ++++
 packages/backend-rs/index.js                  |  6 +-
 packages/backend-rs/src/service/stream.rs     |  1 +
 .../backend-rs/src/service/stream/drive.rs    | 58 +++++++++++++++++++
 .../api/endpoints/drive/files/delete.ts       |  4 +-
 .../api/endpoints/drive/files/update.ts       |  5 +-
 .../api/endpoints/drive/folders/create.ts     |  9 ++-
 .../api/endpoints/drive/folders/delete.ts     |  4 +-
 .../api/endpoints/drive/folders/update.ts     |  9 ++-
 .../backend/src/services/drive/add-file.ts    | 19 ++++--
 packages/backend/src/services/stream.ts       | 27 ++++-----
 11 files changed, 126 insertions(+), 28 deletions(-)
 create mode 100644 packages/backend-rs/src/service/stream/drive.rs

diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index e7d8bb0e76..00381d8b4d 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -1373,6 +1373,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
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 565ca43ce3..b971a57ecc 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -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, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, publishToNotesStream, 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, safeForSql, formatMilliseconds, getImageSizeFromUrl, isQuote, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, getNoteSummary, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, 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,6 +389,10 @@ 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
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index baf3a278d3..36d7f841fe 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -3,6 +3,7 @@ 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;
diff --git a/packages/backend-rs/src/service/stream/drive.rs b/packages/backend-rs/src/service/stream/drive.rs
new file mode 100644
index 0000000000..c3b49d0db3
--- /dev/null
+++ b/packages/backend-rs/src/service/stream/drive.rs
@@ -0,0 +1,58 @@
+use crate::service::stream::{publish_to_stream, Error, Stream};
+
+#[crate::export]
+pub enum DriveFileEvent {
+    Create,
+    Update,
+    Delete,
+}
+
+#[crate::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
+
+#[crate::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
+}
+
+#[crate::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
+}
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index 062b69b9c5..2b5b3bc7bd 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -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);
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index 4d9567a838..c82cffb4a6 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -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;
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
index 1d9ef1831f..8d8c8b37b4 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
@@ -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;
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
index 1954b43361..a5e80dee44 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
@@ -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);
 });
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
index 7b266548e5..0deb1ef8a8 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
@@ -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;
 });
diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts
index 1c5412ac2d..cf84ebd8f4 100644
--- a/packages/backend/src/services/drive/add-file.ts
+++ b/packages/backend/src/services/drive/add-file.ts
@@ -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");
 
@@ -656,11 +663,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),
+			);
 		});
 	}
 
diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts
index c60d5ce974..f2f5cf76d9 100644
--- a/packages/backend/src/services/stream.ts
+++ b/packages/backend/src/services/stream.ts
@@ -12,7 +12,7 @@ import type {
 	// AntennaStreamTypes,
 	// BroadcastTypes,
 	// ChannelStreamTypes,
-	DriveStreamTypes,
+	// DriveStreamTypes,
 	// GroupMessagingStreamTypes,
 	InternalStreamTypes,
 	MainStreamTypes,
@@ -88,17 +88,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"],
@@ -220,7 +221,7 @@ 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 publishChannelStream = publisher.publishChannelStream;

From 0cb4fbf6b4119de0b003e8c08ad16717cc2188df Mon Sep 17 00:00:00 2001
From: sup39 <dev@sup39.dev>
Date: Wed, 12 Jun 2024 00:49:22 +0800
Subject: [PATCH 04/16] refactor: macro-rs -> macros + macros_impl

Co-authored-by: naskya <m@naskya.net>
---
 .gitlab-ci.yml                                |   2 +-
 Cargo.lock                                    |  18 +-
 Cargo.toml                                    |   5 +-
 Dockerfile                                    |   4 +-
 packages/backend-rs/Cargo.toml                |   2 +-
 packages/backend-rs/src/config/server.rs      |   2 +-
 packages/backend-rs/src/lib.rs                |   2 +-
 packages/macro-rs/macros-impl/Cargo.toml      |  11 +
 packages/macro-rs/macros-impl/src/lib.rs      |   2 +
 packages/macro-rs/macros-impl/src/napi.rs     | 451 ++++++++++++
 packages/macro-rs/macros-impl/src/util/mod.rs |   3 +
 .../macro-rs/macros-impl/src/util/tester.rs   | 121 ++++
 packages/macro-rs/{ => macros}/Cargo.toml     |  11 +-
 packages/macro-rs/macros/src/helper.rs        |  88 +++
 packages/macro-rs/macros/src/lib.rs           |  81 +++
 packages/macro-rs/src/lib.rs                  | 680 ------------------
 16 files changed, 780 insertions(+), 703 deletions(-)
 create mode 100644 packages/macro-rs/macros-impl/Cargo.toml
 create mode 100644 packages/macro-rs/macros-impl/src/lib.rs
 create mode 100644 packages/macro-rs/macros-impl/src/napi.rs
 create mode 100644 packages/macro-rs/macros-impl/src/util/mod.rs
 create mode 100644 packages/macro-rs/macros-impl/src/util/tester.rs
 rename packages/macro-rs/{ => macros}/Cargo.toml (52%)
 create mode 100644 packages/macro-rs/macros/src/helper.rs
 create mode 100644 packages/macro-rs/macros/src/lib.rs
 delete mode 100644 packages/macro-rs/src/lib.rs

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d68ae43810..7b12b3d7a5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -290,7 +290,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=.
diff --git a/Cargo.lock b/Cargo.lock
index 75db34e287..37ac78a5d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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]]
diff --git a/Cargo.toml b/Cargo.toml
index 0e3536a5b3..1fe4e70f51 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/Dockerfile b/Dockerfile
index 5bad58b949..789f242070 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,8 +17,7 @@ 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/
+COPY packages/macro-rs packages/macro-rs/
 
 # Configure pnpm, and install backend-rs dependencies
 RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install
@@ -26,7 +25,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
diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml
index 996a95bce4..790ad0f317 100644
--- a/packages/backend-rs/Cargo.toml
+++ b/packages/backend-rs/Cargo.toml
@@ -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 }
diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 5170b0617e..8991caec99 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -4,7 +4,7 @@ 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")]
diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs
index 6a124de340..dbb3f734a1 100644
--- a/packages/backend-rs/src/lib.rs
+++ b/packages/backend-rs/src/lib.rs
@@ -1,4 +1,4 @@
-use macro_rs::{derive_clone_and_export, export, ts_export};
+use macros::{derive_clone_and_export, export, ts_export};
 
 pub mod config;
 pub mod database;
diff --git a/packages/macro-rs/macros-impl/Cargo.toml b/packages/macro-rs/macros-impl/Cargo.toml
new file mode 100644
index 0000000000..8029372236
--- /dev/null
+++ b/packages/macro-rs/macros-impl/Cargo.toml
@@ -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"] }
diff --git a/packages/macro-rs/macros-impl/src/lib.rs b/packages/macro-rs/macros-impl/src/lib.rs
new file mode 100644
index 0000000000..8310f17148
--- /dev/null
+++ b/packages/macro-rs/macros-impl/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod napi;
+mod util;
diff --git a/packages/macro-rs/macros-impl/src/napi.rs b/packages/macro-rs/macros-impl/src/napi.rs
new file mode 100644
index 0000000000..11d9283d5b
--- /dev/null
+++ b/packages/macro-rs/macros-impl/src/napi.rs
@@ -0,0 +1,451 @@
+//! 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(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(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(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)
+        }
+    }
+}
diff --git a/packages/macro-rs/macros-impl/src/util/mod.rs b/packages/macro-rs/macros-impl/src/util/mod.rs
new file mode 100644
index 0000000000..83be476077
--- /dev/null
+++ b/packages/macro-rs/macros-impl/src/util/mod.rs
@@ -0,0 +1,3 @@
+//! Utilities for developing procedural macros
+
+mod tester;
diff --git a/packages/macro-rs/macros-impl/src/util/tester.rs b/packages/macro-rs/macros-impl/src/util/tester.rs
new file mode 100644
index 0000000000..2762f3d4fc
--- /dev/null
+++ b/packages/macro-rs/macros-impl/src/util/tester.rs
@@ -0,0 +1,121 @@
+//! 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);
+                }
+            )*
+        }
+    };
+}
diff --git a/packages/macro-rs/Cargo.toml b/packages/macro-rs/macros/Cargo.toml
similarity index 52%
rename from packages/macro-rs/Cargo.toml
rename to packages/macro-rs/macros/Cargo.toml
index c805fcce36..6b10d7ac1d 100644
--- a/packages/macro-rs/Cargo.toml
+++ b/packages/macro-rs/macros/Cargo.toml
@@ -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"] }
diff --git a/packages/macro-rs/macros/src/helper.rs b/packages/macro-rs/macros/src/helper.rs
new file mode 100644
index 0000000000..a901ba27e9
--- /dev/null
+++ b/packages/macro-rs/macros/src/helper.rs
@@ -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;
diff --git a/packages/macro-rs/macros/src/lib.rs b/packages/macro-rs/macros/src/lib.rs
new file mode 100644
index 0000000000..533f058a96
--- /dev/null
+++ b/packages/macro-rs/macros/src/lib.rs
@@ -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
+}
diff --git a/packages/macro-rs/src/lib.rs b/packages/macro-rs/src/lib.rs
deleted file mode 100644
index 65ad817e05..0000000000
--- a/packages/macro-rs/src/lib.rs
+++ /dev/null
@@ -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)
-                }
-            }
-        )
-    }
-}

From d824cb319ac5fca3984a0f5a1de5e0a4b01e4b78 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 03:36:47 +0900
Subject: [PATCH 05/16] test (backend-rs): ignore some tests in Miri

---
 packages/backend-rs/src/database/cache.rs            | 2 ++
 packages/backend-rs/src/database/postgresql.rs       | 4 ++++
 packages/backend-rs/src/database/redis.rs            | 4 ++++
 packages/backend-rs/src/federation/nodeinfo/fetch.rs | 1 +
 packages/backend-rs/src/misc/get_image_size.rs       | 2 ++
 packages/backend-rs/src/misc/latest_version.rs       | 1 +
 packages/backend-rs/src/misc/password.rs             | 1 +
 7 files changed, 15 insertions(+)

diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs
index 1dcd81d9ec..2fe192cb1e 100644
--- a/packages/backend-rs/src/database/cache.rs
+++ b/packages/backend-rs/src/database/cache.rs
@@ -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";
diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs
index 3a95cd65fb..fbc7fe7018 100644
--- a/packages/backend-rs/src/database/postgresql.rs
+++ b/packages/backend-rs/src/database/postgresql.rs
@@ -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)
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
index 3207e5cbbd..1b989da08b 100644
--- a/packages/backend-rs/src/database/redis.rs
+++ b/packages/backend-rs/src/database/redis.rs
@@ -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();
 
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index 89a325e597..1004bc6f56 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -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")
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index 31d5bb398f..58f9783fe0 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -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";
 
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index ad1d6b2fc7..58f50d0b0b 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -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)
diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index b23b305e32..507313c778 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -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";
 

From 7db7fdaabedcb393f84ad84faff59c77d90f8bd3 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 03:37:01 +0900
Subject: [PATCH 06/16] chore: format

---
 packages/macro-rs/macros-impl/src/util/tester.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/macro-rs/macros-impl/src/util/tester.rs b/packages/macro-rs/macros-impl/src/util/tester.rs
index 2762f3d4fc..717ed5fb19 100644
--- a/packages/macro-rs/macros-impl/src/util/tester.rs
+++ b/packages/macro-rs/macros-impl/src/util/tester.rs
@@ -77,7 +77,8 @@ macro_rules! macro_doctest {
 ///     }
 /// }
 /// ```
-#[macro_export] macro_rules! macro_unit_tests {
+#[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)),

From 9502289d8997949c127bfa168d1554f3d76662f4 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 03:38:31 +0900
Subject: [PATCH 07/16] chore (backend-rs): remove crate-level `use`s

---
 packages/backend-rs/src/config/constant.rs    | 14 +++++-----
 packages/backend-rs/src/config/meta.rs        |  8 +++---
 packages/backend-rs/src/config/server.rs      | 28 +++++++++----------
 packages/backend-rs/src/federation/acct.rs    |  6 ++--
 .../src/federation/nodeinfo/fetch.rs          |  2 +-
 .../src/federation/nodeinfo/generate.rs       |  6 ++--
 .../src/federation/nodeinfo/schema.rs         | 16 +++++------
 packages/backend-rs/src/init/greet.rs         |  2 +-
 packages/backend-rs/src/init/log.rs           |  2 +-
 packages/backend-rs/src/init/system_info.rs   |  2 +-
 packages/backend-rs/src/lib.rs                |  2 --
 .../backend-rs/src/misc/check_server_block.rs |  6 ++--
 .../backend-rs/src/misc/check_word_mute.rs    |  4 +--
 packages/backend-rs/src/misc/convert_host.rs  | 10 +++----
 packages/backend-rs/src/misc/emoji.rs         |  2 +-
 packages/backend-rs/src/misc/escape_sql.rs    |  4 +--
 .../src/misc/format_milliseconds.rs           |  2 +-
 .../backend-rs/src/misc/get_image_size.rs     |  4 +--
 packages/backend-rs/src/misc/is_quote.rs      |  2 +-
 packages/backend-rs/src/misc/is_safe_url.rs   |  2 +-
 .../backend-rs/src/misc/latest_version.rs     |  2 +-
 packages/backend-rs/src/misc/mastodon_id.rs   |  4 +--
 .../backend-rs/src/misc/note/summarize.rs     |  2 +-
 packages/backend-rs/src/misc/nyaify.rs        |  2 +-
 packages/backend-rs/src/misc/password.rs      |  6 ++--
 packages/backend-rs/src/misc/reaction.rs      |  8 +++---
 .../misc/remove_old_attestation_challenges.rs |  2 +-
 packages/backend-rs/src/misc/system_info.rs   | 14 +++++-----
 .../src/service/antenna/process_new_note.rs   |  2 +-
 .../backend-rs/src/service/antenna/update.rs  |  2 +-
 packages/backend-rs/src/service/note/watch.rs |  4 +--
 .../src/service/push_notification.rs          |  4 +--
 packages/backend-rs/src/service/stream.rs     |  2 +-
 .../backend-rs/src/service/stream/channel.rs  |  2 +-
 .../backend-rs/src/service/stream/chat.rs     |  2 +-
 .../src/service/stream/chat_index.rs          |  4 +--
 .../src/service/stream/custom_emoji.rs        |  4 +--
 .../backend-rs/src/service/stream/drive.rs    |  8 +++---
 .../src/service/stream/group_chat.rs          |  2 +-
 .../src/service/stream/moderation.rs          |  4 +--
 .../backend-rs/src/service/stream/notes.rs    |  2 +-
 packages/backend-rs/src/util/id.rs            |  6 ++--
 packages/backend-rs/src/util/random.rs        |  4 +--
 43 files changed, 107 insertions(+), 109 deletions(-)

diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs
index f2340893cd..7e6b50fa52 100644
--- a/packages/backend-rs/src/config/constant.rs
+++ b/packages/backend-rs/src/config/constant.rs
@@ -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",
diff --git a/packages/backend-rs/src/config/meta.rs b/packages/backend-rs/src/config/meta.rs
index cedb6cf0ad..969d9d35f5 100644
--- a/packages/backend-rs/src/config/meta.rs
+++ b/packages/backend-rs/src/config/meta.rs
@@ -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();
diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 8991caec99..81f2baedb0 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -8,7 +8,7 @@ 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();
diff --git a/packages/backend-rs/src/federation/acct.rs b/packages/backend-rs/src/federation/acct.rs
index b07cb71448..816e75baf2 100644
--- a/packages/backend-rs/src/federation/acct.rs
+++ b/packages/backend-rs/src/federation/acct.rs
@@ -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()
 }
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index 1004bc6f56..4f11821054 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -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?;
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index b37652637c..4d0a007823 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -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(())
diff --git a/packages/backend-rs/src/federation/nodeinfo/schema.rs b/packages/backend-rs/src/federation/nodeinfo/schema.rs
index 82f1dfe728..4abb02562e 100644
--- a/packages/backend-rs/src/federation/nodeinfo/schema.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/schema.rs
@@ -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>,
diff --git a/packages/backend-rs/src/init/greet.rs b/packages/backend-rs/src/init/greet.rs
index 323548e59c..01f795ed00 100644
--- a/packages/backend-rs/src/init/greet.rs
+++ b/packages/backend-rs/src/init/greet.rs
@@ -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);
 
diff --git a/packages/backend-rs/src/init/log.rs b/packages/backend-rs/src/init/log.rs
index beb49decdc..47466735e3 100644
--- a/packages/backend-rs/src/init/log.rs
+++ b/packages/backend-rs/src/init/log.rs
@@ -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();
 
diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs
index c4847cdd0d..7fde335e85 100644
--- a/packages/backend-rs/src/init/system_info.rs
+++ b/packages/backend-rs/src/init/system_info.rs
@@ -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()?;
 
diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs
index dbb3f734a1..1b9933fb8a 100644
--- a/packages/backend-rs/src/lib.rs
+++ b/packages/backend-rs/src/lib.rs
@@ -1,5 +1,3 @@
-use macros::{derive_clone_and_export, export, ts_export};
-
 pub mod config;
 pub mod database;
 pub mod federation;
diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs
index 885507a9ae..a6355f5e94 100644
--- a/packages/backend-rs/src/misc/check_server_block.rs
+++ b/packages/backend-rs/src/misc/check_server_block.rs
@@ -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?;
 
diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs
index 0faf946c8d..fdf4e1a8a0 100644
--- a/packages/backend-rs/src/misc/check_word_mute.rs
+++ b/packages/backend-rs/src/misc/check_word_mute.rs
@@ -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],
diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs
index 2850d4cb29..0de4d1912d 100644
--- a/packages/backend-rs/src/misc/convert_host.rs
+++ b/packages/backend-rs/src/misc/convert_host.rs
@@ -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)
 }
diff --git a/packages/backend-rs/src/misc/emoji.rs b/packages/backend-rs/src/misc/emoji.rs
index a19fc6229d..47c2e0debe 100644
--- a/packages/backend-rs/src/misc/emoji.rs
+++ b/packages/backend-rs/src/misc/emoji.rs
@@ -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()
 }
diff --git a/packages/backend-rs/src/misc/escape_sql.rs b/packages/backend-rs/src/misc/escape_sql.rs
index 5ff7e51729..747ceb51c7 100644
--- a/packages/backend-rs/src/misc/escape_sql.rs
+++ b/packages/backend-rs/src/misc/escape_sql.rs
@@ -1,11 +1,11 @@
 /// Escapes `%` and `\` in the given string.
-#[crate::export]
+#[macros::export]
 pub fn sql_like_escape(src: &str) -> String {
     src.replace('%', r"\%").replace('_', r"\_")
 }
 
 /// 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', '"', '\'', '\\', '%',
diff --git a/packages/backend-rs/src/misc/format_milliseconds.rs b/packages/backend-rs/src/misc/format_milliseconds.rs
index b30af15847..148e5791b5 100644
--- a/packages/backend-rs/src/misc/format_milliseconds.rs
+++ b/packages/backend-rs/src/misc/format_milliseconds.rs
@@ -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;
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index 58f9783fe0..1964c9cbd9 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -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;
 
diff --git a/packages/backend-rs/src/misc/is_quote.rs b/packages/backend-rs/src/misc/is_quote.rs
index 42e792f956..3ee15a5873 100644
--- a/packages/backend-rs/src/misc/is_quote.rs
+++ b/packages/backend-rs/src/misc/is_quote.rs
@@ -4,7 +4,7 @@ use crate::model::entity::note;
 // https://github.com/napi-rs/napi-rs/issues/2060
 type Note = note::Model;
 
-#[crate::export]
+#[macros::export]
 pub fn is_quote(note: Note) -> bool {
     note.renote_id.is_some() && (note.text.is_some() || note.has_poll || !note.file_ids.is_empty())
 }
diff --git a/packages/backend-rs/src/misc/is_safe_url.rs b/packages/backend-rs/src/misc/is_safe_url.rs
index 1e5c5244ce..603710310f 100644
--- a/packages/backend-rs/src/misc/is_safe_url.rs
+++ b/packages/backend-rs/src/misc/is_safe_url.rs
@@ -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"
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 58f50d0b0b..1f330f4d2e 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -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?;
diff --git a/packages/backend-rs/src/misc/mastodon_id.rs b/packages/backend-rs/src/misc/mastodon_id.rs
index bb837c42f7..65fbd4d057 100644
--- a/packages/backend-rs/src/misc/mastodon_id.rs
+++ b/packages/backend-rs/src/misc/mastodon_id.rs
@@ -1,10 +1,10 @@
-#[crate::export]
+#[macros::export]
 pub fn to_mastodon_id(firefish_id: &str) -> Option<String> {
     let decoded: [u8; 16] = basen::BASE36.decode_var_len(firefish_id)?;
     Some(basen::BASE10.encode_var_len(&decoded))
 }
 
-#[crate::export]
+#[macros::export]
 pub fn from_mastodon_id(mastodon_id: &str) -> Option<String> {
     let decoded: [u8; 16] = basen::BASE10.decode_var_len(mastodon_id)?;
     Some(basen::BASE36.encode_var_len(&decoded))
diff --git a/packages/backend-rs/src/misc/note/summarize.rs b/packages/backend-rs/src/misc/note/summarize.rs
index 83bbb0246d..9649fc9345 100644
--- a/packages/backend-rs/src/misc/note/summarize.rs
+++ b/packages/backend-rs/src/misc/note/summarize.rs
@@ -1,4 +1,4 @@
-#[crate::export(js_name = "getNoteSummary")]
+#[macros::export(js_name = "getNoteSummary")]
 pub fn summarize_impl(
     file_ids: &[String],
     text: Option<String>,
diff --git a/packages/backend-rs/src/misc/nyaify.rs b/packages/backend-rs/src/misc/nyaify.rs
index 5e343f5838..c58795f798 100644
--- a/packages/backend-rs/src/misc/nyaify.rs
+++ b/packages/backend-rs/src/misc/nyaify.rs
@@ -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();
 
diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index 507313c778..bc2025f275 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -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")
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index 662e6ac766..01441b90a3 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -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?
diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
index a36c34c664..725f6ac481 100644
--- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
+++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
@@ -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)))
diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs
index 2c4f6cdcf2..4b31ceaaee 100644
--- a/packages/backend-rs/src/misc/system_info.rs
+++ b/packages/backend-rs/src/misc/system_info.rs
@@ -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();
diff --git a/packages/backend-rs/src/service/antenna/process_new_note.rs b/packages/backend-rs/src/service/antenna/process_new_note.rs
index 7e86fbd31f..13fc7fdcbb 100644
--- a/packages/backend-rs/src/service/antenna/process_new_note.rs
+++ b/packages/backend-rs/src/service/antenna/process_new_note.rs
@@ -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,
diff --git a/packages/backend-rs/src/service/antenna/update.rs b/packages/backend-rs/src/service/antenna/update.rs
index 350b9ba9db..169e574379 100644
--- a/packages/backend-rs/src/service/antenna/update.rs
+++ b/packages/backend-rs/src/service/antenna/update.rs
@@ -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(())
diff --git a/packages/backend-rs/src/service/note/watch.rs b/packages/backend-rs/src/service/note/watch.rs
index d63ce00e85..144f4e2360 100644
--- a/packages/backend-rs/src/service/note/watch.rs
+++ b/packages/backend-rs/src/service/note/watch.rs
@@ -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?;
 
diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs
index 8bde9987d3..29de12fca5 100644
--- a/packages/backend-rs/src/service/push_notification.rs
+++ b/packages/backend-rs/src/service/push_notification.rs
@@ -31,7 +31,7 @@ fn get_client() -> Result<IsahcWebPushClient, Error> {
         .cloned()?)
 }
 
-#[crate::export]
+#[macros::export]
 pub enum PushNotificationKind {
     Generic,
     Chat,
@@ -129,7 +129,7 @@ async fn handle_web_push_failure(
     Ok(())
 }
 
-#[crate::export]
+#[macros::export]
 pub async fn send_push_notification(
     receiver_user_id: &str,
     kind: PushNotificationKind,
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index 36d7f841fe..412ed3a3a7 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -54,7 +54,7 @@ pub enum Stream {
     },
 }
 
-#[crate::export]
+#[macros::export]
 pub enum ChatEvent {
     Message,
     Read,
diff --git a/packages/backend-rs/src/service/stream/channel.rs b/packages/backend-rs/src/service/stream/channel.rs
index 028aab69c7..b1bb865fa1 100644
--- a/packages/backend-rs/src/service/stream/channel.rs
+++ b/packages/backend-rs/src/service/stream/channel.rs
@@ -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 },
diff --git a/packages/backend-rs/src/service/stream/chat.rs b/packages/backend-rs/src/service/stream/chat.rs
index 2f60ff4015..88532c7e86 100644
--- a/packages/backend-rs/src/service/stream/chat.rs
+++ b/packages/backend-rs/src/service/stream/chat.rs
@@ -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,
diff --git a/packages/backend-rs/src/service/stream/chat_index.rs b/packages/backend-rs/src/service/stream/chat_index.rs
index e686db8149..25b2913f2c 100644
--- a/packages/backend-rs/src/service/stream/chat_index.rs
+++ b/packages/backend-rs/src/service/stream/chat_index.rs
@@ -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,
diff --git a/packages/backend-rs/src/service/stream/custom_emoji.rs b/packages/backend-rs/src/service/stream/custom_emoji.rs
index 164d89b957..890d550d6a 100644
--- a/packages/backend-rs/src/service/stream/custom_emoji.rs
+++ b/packages/backend-rs/src/service/stream/custom_emoji.rs
@@ -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,
diff --git a/packages/backend-rs/src/service/stream/drive.rs b/packages/backend-rs/src/service/stream/drive.rs
index c3b49d0db3..21ddf1f4b2 100644
--- a/packages/backend-rs/src/service/stream/drive.rs
+++ b/packages/backend-rs/src/service/stream/drive.rs
@@ -1,13 +1,13 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[crate::export]
+#[macros::export]
 pub enum DriveFileEvent {
     Create,
     Update,
     Delete,
 }
 
-#[crate::export]
+#[macros::export]
 pub enum DriveFolderEvent {
     Create,
     Update,
@@ -17,7 +17,7 @@ pub enum DriveFolderEvent {
 // 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
 
-#[crate::export(js_name = "publishToDriveFileStream")]
+#[macros::export(js_name = "publishToDriveFileStream")]
 pub async fn publish_file(
     user_id: String,
     kind: DriveFileEvent,
@@ -37,7 +37,7 @@ pub async fn publish_file(
     .await
 }
 
-#[crate::export(js_name = "publishToDriveFolderStream")]
+#[macros::export(js_name = "publishToDriveFolderStream")]
 pub async fn publish_folder(
     user_id: String,
     kind: DriveFolderEvent,
diff --git a/packages/backend-rs/src/service/stream/group_chat.rs b/packages/backend-rs/src/service/stream/group_chat.rs
index 49ebc299d2..005a523f1c 100644
--- a/packages/backend-rs/src/service/stream/group_chat.rs
+++ b/packages/backend-rs/src/service/stream/group_chat.rs
@@ -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,
diff --git a/packages/backend-rs/src/service/stream/moderation.rs b/packages/backend-rs/src/service/stream/moderation.rs
index 218441e15f..3ac261ff50 100644
--- a/packages/backend-rs/src/service/stream/moderation.rs
+++ b/packages/backend-rs/src/service/stream/moderation.rs
@@ -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 },
diff --git a/packages/backend-rs/src/service/stream/notes.rs b/packages/backend-rs/src/service/stream/notes.rs
index 6c2336e347..8832bc03e3 100644
--- a/packages/backend-rs/src/service/stream/notes.rs
+++ b/packages/backend-rs/src/service/stream/notes.rs
@@ -7,7 +7,7 @@ use crate::{
 // https://github.com/napi-rs/napi-rs/issues/2060
 type Note = note::Model;
 
-#[crate::export(js_name = "publishToNotesStream")]
+#[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
 }
diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs
index f61454d9c3..2d384db78c 100644
--- a/packages/backend-rs/src/util/id.rs
+++ b/packages/backend-rs/src/util/id.rs
@@ -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())
 }
diff --git a/packages/backend-rs/src/util/random.rs b/packages/backend-rs/src/util/random.rs
index 738926576d..0c35657a47 100644
--- a/packages/backend-rs/src/util/random.rs
+++ b/packages/backend-rs/src/util/random.rs
@@ -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)
 }

From e6735519dfd5bbc85f9d09e9ce78666598ddcc4c Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 03:50:40 +0900
Subject: [PATCH 08/16] chore (macros-impl): allow
 clippy::items_after_test_module

---
 packages/macro-rs/macros-impl/src/lib.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/macro-rs/macros-impl/src/lib.rs b/packages/macro-rs/macros-impl/src/lib.rs
index 8310f17148..9e4a70240e 100644
--- a/packages/macro-rs/macros-impl/src/lib.rs
+++ b/packages/macro-rs/macros-impl/src/lib.rs
@@ -1,2 +1,4 @@
+#![allow(clippy::items_after_test_module)]
+
 pub mod napi;
 mod util;

From 84c4701a493b4700b00ab05df46728d56d0ee6f1 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Tue, 11 Jun 2024 20:05:04 +0000
Subject: [PATCH 09/16] chore(deps): update dependency webpack to v5.92.0

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 36 +++++++++++++++++------------------
 2 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index abb315b15c..756fb76a5a 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -177,7 +177,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"
 	}
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44cea2104b..4c893b0f37 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -490,10 +490,10 @@ importers:
         version: 2.0.0
       swc-loader:
         specifier: 0.2.6
-        version: 0.2.6(@swc/core@1.5.28)(webpack@5.91.0(@swc/core@1.5.28))
+        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.28))
+        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.28)(@swc/wasm@1.2.130)(@types/node@20.14.2)(typescript@5.4.5)
@@ -507,8 +507,8 @@ importers:
         specifier: 5.4.5
         version: 5.4.5
       webpack:
-        specifier: 5.91.0
-        version: 5.91.0(@swc/core@1.5.28)
+        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)
@@ -2847,8 +2847,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
 
@@ -7841,8 +7841,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:
@@ -10319,7 +10319,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
 
@@ -15468,11 +15468,11 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  swc-loader@0.2.6(@swc/core@1.5.28)(webpack@5.91.0(@swc/core@1.5.28)):
+  swc-loader@0.2.6(@swc/core@1.5.28)(webpack@5.92.0(@swc/core@1.5.28)):
     dependencies:
       '@swc/core': 1.5.28
       '@swc/counter': 0.1.3
-      webpack: 5.91.0(@swc/core@1.5.28)
+      webpack: 5.92.0(@swc/core@1.5.28)
 
   swiper@11.1.4: {}
 
@@ -15513,14 +15513,14 @@ snapshots:
       fast-fifo: 1.3.2
       streamx: 2.18.0
 
-  terser-webpack-plugin@5.3.10(@swc/core@1.5.28)(webpack@5.91.0(@swc/core@1.5.28)):
+  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.1
-      webpack: 5.91.0(@swc/core@1.5.28)
+      webpack: 5.92.0(@swc/core@1.5.28)
     optionalDependencies:
       '@swc/core': 1.5.28
 
@@ -15665,7 +15665,7 @@ snapshots:
       '@jest/types': 29.6.3
       babel-jest: 29.7.0(@babel/core@7.24.7)
 
-  ts-loader@9.5.1(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.5.28)):
+  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.17.0
@@ -15673,7 +15673,7 @@ snapshots:
       semver: 7.6.2
       source-map: 0.7.4
       typescript: 5.4.5
-      webpack: 5.91.0(@swc/core@1.5.28)
+      webpack: 5.92.0(@swc/core@1.5.28)
 
   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:
@@ -16048,7 +16048,7 @@ snapshots:
 
   webpack-sources@3.2.3: {}
 
-  webpack@5.91.0(@swc/core@1.5.28):
+  webpack@5.92.0(@swc/core@1.5.28):
     dependencies:
       '@types/eslint-scope': 3.7.7
       '@types/estree': 1.0.5
@@ -16056,7 +16056,7 @@ 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.17.0
@@ -16071,7 +16071,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.28)(webpack@5.91.0(@swc/core@1.5.28))
+      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:

From eb5372489f4a91a6f929d4d7d1d92953793b8128 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Tue, 11 Jun 2024 20:05:26 +0000
Subject: [PATCH 10/16] fix(deps): update dependency @redocly/openapi-core to
 v1.15.0

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 18 +++++++++---------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index abb315b15c..429b564b6d 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -31,7 +31,7 @@
 		"@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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44cea2104b..b18a3a3ac9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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
@@ -2014,11 +2014,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':
@@ -9316,12 +9316,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

From 175fbca72098cd8c06db1132ee20b1bc3d8cada1 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Tue, 11 Jun 2024 20:05:44 +0000
Subject: [PATCH 11/16] fix(deps): update dependency aws-sdk to v2.1639.0

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index abb315b15c..add36b500f 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -36,7 +36,7 @@
 		"adm-zip": "0.5.14",
 		"ajv": "8.16.0",
 		"archiver": "7.0.1",
-		"aws-sdk": "2.1638.0",
+		"aws-sdk": "2.1639.0",
 		"axios": "1.7.2",
 		"backend-rs": "workspace:*",
 		"blurhash": "2.0.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44cea2104b..a629dfd232 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -85,8 +85,8 @@ importers:
         specifier: 7.0.1
         version: 7.0.1
       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
@@ -3040,8 +3040,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:
@@ -10512,7 +10512,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
@@ -10712,7 +10712,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:
@@ -16195,7 +16195,7 @@ snapshots:
 
   xml2js@0.6.2:
     dependencies:
-      sax: 1.2.1
+      sax: 1.4.1
       xmlbuilder: 11.0.1
 
   xmlbuilder@11.0.1: {}

From 16720d6c146fb65a61dc486afcd6274a8c39a6de Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Wed, 12 Jun 2024 04:06:23 +0000
Subject: [PATCH 12/16] chore(deps): update dependency sass to v1.77.5

---
 packages/client/package.json |  2 +-
 pnpm-lock.yaml               | 32 ++++++++++++++++----------------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/packages/client/package.json b/packages/client/package.json
index 3b5d47929a..5d9696ee5d 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44a24cdd6f..9a367b3ff9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -586,7 +586,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.1))(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.1))(vue@3.4.27(typescript@5.4.5))
       '@vue/runtime-core':
         specifier: 3.4.27
         version: 3.4.27
@@ -702,8 +702,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
@@ -739,10 +739,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.1)
+        version: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1)
       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.1))
+        version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1))
       vue:
         specifier: 3.4.27
         version: 3.4.27(typescript@5.4.5)
@@ -931,10 +931,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.1)
+        version: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1)
       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.1))
+        version: 0.5.1(vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1))
 
 packages:
 
@@ -6892,8 +6892,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
 
@@ -10136,9 +10136,9 @@ snapshots:
 
   '@ungap/structured-clone@1.2.0': {}
 
-  '@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.1))(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.1))(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.1)
+      vite: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1)
       vue: 3.4.27(typescript@5.4.5)
 
   '@volar/language-core@2.3.0':
@@ -15081,7 +15081,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
@@ -15939,16 +15939,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.1)):
+  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.1)):
     dependencies:
       chalk: 4.1.2
       debug: 4.3.5
       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.1)
+      vite: 5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.2.13(@types/node@20.14.2)(sass@1.77.4)(stylus@0.57.0)(terser@5.31.1):
+  vite@5.2.13(@types/node@20.14.2)(sass@1.77.5)(stylus@0.57.0)(terser@5.31.1):
     dependencies:
       esbuild: 0.20.2
       postcss: 8.4.38
@@ -15956,7 +15956,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.1
 

From 9f2788888f03e23472209cb4d0710a0927923782 Mon Sep 17 00:00:00 2001
From: CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Date: Wed, 12 Jun 2024 04:07:06 +0000
Subject: [PATCH 13/16] fix(deps): update dependency bull to v4.13.0

---
 packages/backend/package.json |  2 +-
 pnpm-lock.yaml                | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 7776a1ffaf..06683e0e23 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -40,7 +40,7 @@
 		"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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44a24cdd6f..3153a2e544 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,8 +97,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
@@ -3201,8 +3201,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:
@@ -10735,7 +10735,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

From e60db9e254a04b14f06606007f9da9366ddf0202 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 19:43:52 +0900
Subject: [PATCH 14/16] chore (macros-impl): add linebreaks to napi error
 reason

---
 packages/macro-rs/macros-impl/src/napi.rs | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/packages/macro-rs/macros-impl/src/napi.rs b/packages/macro-rs/macros-impl/src/napi.rs
index 11d9283d5b..13e2242ab8 100644
--- a/packages/macro-rs/macros-impl/src/napi.rs
+++ b/packages/macro-rs/macros-impl/src/napi.rs
@@ -177,7 +177,9 @@ use quote::{quote, ToTokens};
 /// #[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)))
+///         .map_err(|err| napi::Error::from_reason(
+///             format!("\n{}\n", crate::util::error_chain::format_error(&err))
+///         ))
 /// }
 /// # });
 /// ```
@@ -248,7 +250,9 @@ pub fn napi(macro_attr: TokenStream, item: TokenStream) -> TokenStream {
             };
             // 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)))
+                .map_err(|err| napi::Error::from_reason(
+                    format!("\n{}\n", crate::util::error_chain::format_error(&err))
+                ))
             });
         }
     };
@@ -382,7 +386,9 @@ crate::macro_unit_tests! {
             divisor: i64,
         ) -> napi::Result<i64> {
             integer_divide(dividend, divisor)
-                .map_err(|err| napi::Error::from_reason(crate::util::error_chain::format_error(&err)))
+                .map_err(|err| napi::Error::from_reason(
+                    format!("\n{}\n", crate::util::error_chain::format_error(&err))
+                ))
         }
     }
 

From 8931bc708ed09c7f21e1e1fb157f37372b76bb5d Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 20:00:44 +0900
Subject: [PATCH 15/16] test (backend-rs): minor refactoring

---
 packages/backend-rs/src/util/error_chain.rs | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)

diff --git a/packages/backend-rs/src/util/error_chain.rs b/packages/backend-rs/src/util/error_chain.rs
index 086e5449f2..4d1341dc05 100644
--- a/packages/backend-rs/src/util/error_chain.rs
+++ b/packages/backend-rs/src/util/error_chain.rs
@@ -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..]);
     }
 }

From 10a1fb33645e7c05b3295f77b5d1cc0e4afed960 Mon Sep 17 00:00:00 2001
From: naskya <m@naskya.net>
Date: Wed, 12 Jun 2024 20:12:22 +0900
Subject: [PATCH 16/16] container: rearrange operations for cache efficiency

---
 Dockerfile | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 789f242070..72c1290e50 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,22 +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 packages/macro-rs/
 
 # Configure pnpm, and install backend-rs dependencies
 RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm --filter backend-rs install