diff --git a/.env.local b/.env.local index 34b87a66..f8079111 100644 --- a/.env.local +++ b/.env.local @@ -70,3 +70,9 @@ GENARRATIVE_SPACETIME_TOKEN="" GENARRATIVE_ADMIN_USERNAME=admin GENARRATIVE_ADMIN_PASSWORD=123456 ADMIN_API_TARGET=http://127.0.0.1:3100 + +# OTLP +GENARRATIVE_OTEL_ENABLED=true +OTEL_SERVICE_NAME=genarrative-api +OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 +OTEL_RESOURCE_ATTRIBUTES=deployment.environment=local,service.namespace=genarrative diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index c8893c4a..78fa5b46 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,6 +16,15 @@ --- +## 2026-05-16 api-server OpenTelemetry 统一补齐 traces metrics logs + +- 背景:压测与运行观测需要把 HTTP、SpacetimeDB 调用和应用日志串起来,同时保留本地 `journalctl` / 文件日志做故障排障。 +- 决策:`api-server` 通过 OTLP HTTP base endpoint 发送 traces、metrics 和 logs;Collector 统一用 `otelcol-contrib`,`npm run otel:debug` 负责 debug 采集,`npm run otel:rider` 负责转发到 Rider;Rider 只是接收与可视化端,不直接替代 Collector。 +- 日志口径:Rider Logs 面板只展示 log event 自身字段,请求完成日志需要直接携带 `request_id`、HTTP method、规范化 route、scheme、path、status、status_class、latency 和 slow_request;更完整的 request attributes 仍以 trace/span 为准。 +- 影响范围:`server-rs/crates/shared-logging`、`server-rs/crates/api-server`、`scripts/run-otelcol.mjs`、压测与运维文档。 +- 验证方式:`cargo test -p shared-logging --manifest-path server-rs/Cargo.toml generic_otlp_http_endpoint_expands_to_signal_paths`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml observability_route_keeps_metrics_labels_low_cardinality`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml resolve_request_scheme_uses_forwarded_proto_first_value`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。 +- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`scripts/loadtest/README.md`。 + ## 2026-05-14 创作页图像输入统一封装为图像组件 - 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。 diff --git a/.hermes/shared-memory/development-workflow.md b/.hermes/shared-memory/development-workflow.md index 49bff2ef..9b077958 100644 --- a/.hermes/shared-memory/development-workflow.md +++ b/.hermes/shared-memory/development-workflow.md @@ -195,6 +195,13 @@ npm run check:server-rs-ddd - `docs/technical/SPACETIMEDB_TABLE_CATALOG.md` - `docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md` +## 生产压测与观测默认口径 + +- 作品列表 50 HTTP req/s 压测使用 `scripts/loadtest/README.md` 中的 K6 命令;当前脚本一次 iteration 请求两个公开列表接口,因此目标 50 HTTP req/s 对应 `PEAK_RPS=25`。 +- 生产 `api-server` 默认 backlog、worker threads、systemd 限制、Nginx upstream timing log 和 OTLP 开关以 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md` 为准。 +- OpenTelemetry 现阶段可选发送 traces / metrics / logs,但不会取代本地 `journalctl -u genarrative-api.service`、`logs/api-server/` 与 `/var/log/nginx/genarrative.*.log`。 +- 指标 label 不写 raw URI、userId、profileId 或 request_id;request_id 只用于 trace/log 串联。 + ## 前端相关默认验证 前端修改后,应根据修改范围选择: diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 79e0216d..831163a6 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -374,6 +374,14 @@ - 验证:请求返回 JSON,相关页面不再出现 HTML parse 错误。 - 关联:`docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md`。 +## `npm run build` 因 Vite warning 被 build-gate 判失败 + +- 现象:主站或后台 Vite 已经输出 `built in ...`,但根命令最后仍失败并打印 `Build gate failed because warnings were emitted`。 +- 原因:`scripts/build-gate.mjs` 会收集 stdout / stderr 中的 warning 行并作为硬失败;常见触发是产物 chunk 超过 `vite.config.ts` 或 `apps/admin-web/vite.config.ts` 的 `chunkSizeWarningLimit`。 +- 处理:先看 warning 原文确认来源。若是合理的入口级 chunk 体积增长,调整对应 Vite 配置阈值或做真实拆包;不要把这类失败按 Rust / SpacetimeDB 编译错误排查。 +- 验证:重新执行 `npm run build`,主站与后台均构建完成且没有 build-gate warning 汇总。 +- 关联:`scripts/build-gate.mjs`、`vite.config.ts`、`apps/admin-web/vite.config.ts`。 + ## 反馈页清空 file input 前必须先拷贝 FileList - 现象:点击上传凭证会打开文件选择框,但选择图片后页面没有展示预览,提交时也没有携带图片凭证。 diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index f6906f2e..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# 已忽略包含查询文件的默认文件夹 -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index cf8f80f7..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -mod.rs \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 932f7d1b..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml deleted file mode 100644 index ead1d8a3..00000000 --- a/.idea/editor.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549e..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 315bbf8a..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml deleted file mode 100644 index b0c1c68f..00000000 --- a/.idea/prettier.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/deploy/env/api-server.env.example b/deploy/env/api-server.env.example index 7420d6c9..1d727052 100644 --- a/deploy/env/api-server.env.example +++ b/deploy/env/api-server.env.example @@ -5,6 +5,12 @@ GENARRATIVE_ENV=production GENARRATIVE_API_HOST=127.0.0.1 GENARRATIVE_API_PORT=8082 GENARRATIVE_API_LOG=info,tower_http=info +GENARRATIVE_API_LISTEN_BACKLOG=1024 +GENARRATIVE_API_WORKER_THREADS=4 +GENARRATIVE_OTEL_ENABLED=false +OTEL_SERVICE_NAME=genarrative-api +OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 +OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=genarrative GENARRATIVE_ADMIN_USERNAME= GENARRATIVE_ADMIN_PASSWORD= diff --git a/deploy/nginx/genarrative-dev-http.conf b/deploy/nginx/genarrative-dev-http.conf index 824a8f5a..d9e85b16 100644 --- a/deploy/nginx/genarrative-dev-http.conf +++ b/deploy/nginx/genarrative-dev-http.conf @@ -1,9 +1,23 @@ # 开发服无域名时使用的 HTTP 入口,只允许用于 DEPLOY_TARGET=development。 # 没有域名时,将 SERVER_NAME 填为开发机 IP 或临时主机名。 # 生产 release 仍必须使用 genarrative.conf 的 HTTPS 配置。 +log_format genarrative_upstream + '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' + 'request_time=$request_time upstream_connect_time=$upstream_connect_time ' + 'upstream_header_time=$upstream_header_time upstream_response_time=$upstream_response_time ' + 'upstream_status=$upstream_status request_id=$request_id'; + +upstream genarrative_api { + server 127.0.0.1:8082; + keepalive 64; +} + server { listen 80; server_name genarrative.example.com; + access_log /var/log/nginx/genarrative.access.log genarrative_upstream; + error_log /var/log/nginx/genarrative.error.log warn; gzip on; gzip_vary on; @@ -34,8 +48,9 @@ server { return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}'; } - proxy_pass http://127.0.0.1:8082/admin/api/; + proxy_pass http://genarrative_api/admin/api/; proxy_http_version 1.1; + proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -73,12 +88,13 @@ server { return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}'; } - proxy_pass http://127.0.0.1:8082; + proxy_pass http://genarrative_api; proxy_http_version 1.1; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; add_header X-Accel-Buffering no always; + proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/deploy/nginx/genarrative.conf b/deploy/nginx/genarrative.conf index 06a3bf86..e0854442 100644 --- a/deploy/nginx/genarrative.conf +++ b/deploy/nginx/genarrative.conf @@ -1,7 +1,21 @@ # 生产域名需要在部署前替换为真实域名,并由 certbot 或等价流程写入 HTTPS 证书配置。 +log_format genarrative_upstream + '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' + 'request_time=$request_time upstream_connect_time=$upstream_connect_time ' + 'upstream_header_time=$upstream_header_time upstream_response_time=$upstream_response_time ' + 'upstream_status=$upstream_status request_id=$request_id'; + +upstream genarrative_api { + server 127.0.0.1:8082; + keepalive 64; +} + server { listen 80; server_name genarrative.example.com; + access_log /var/log/nginx/genarrative.access.log genarrative_upstream; + error_log /var/log/nginx/genarrative.error.log warn; location /.well-known/acme-challenge/ { root /var/www/html; @@ -15,6 +29,8 @@ server { server { listen 443 ssl http2; server_name genarrative.example.com; + access_log /var/log/nginx/genarrative.access.log genarrative_upstream; + error_log /var/log/nginx/genarrative.error.log warn; gzip on; gzip_vary on; @@ -48,8 +64,9 @@ server { return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}'; } - proxy_pass http://127.0.0.1:8082/admin/api/; + proxy_pass http://genarrative_api/admin/api/; proxy_http_version 1.1; + proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -87,12 +104,13 @@ server { return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}'; } - proxy_pass http://127.0.0.1:8082; + proxy_pass http://genarrative_api; proxy_http_version 1.1; proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; add_header X-Accel-Buffering no always; + proxy_set_header Connection ""; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/deploy/systemd/genarrative-api.service b/deploy/systemd/genarrative-api.service index 1a22b75d..bba53a79 100644 --- a/deploy/systemd/genarrative-api.service +++ b/deploy/systemd/genarrative-api.service @@ -15,6 +15,8 @@ Restart=always RestartSec=5 KillSignal=SIGINT TimeoutStopSec=30 +LimitNOFILE=65535 +TasksMax=2048 # api-server 只读发布目录,运行态写入必须显式落到环境变量指定的服务端私有目录。 NoNewPrivileges=true diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 6cc9b533..1709ffc4 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -79,6 +79,8 @@ npm run lint npm run check ``` +`npm run build` 由 `scripts/build-gate.mjs` 串行构建主站和后台;该门禁会把 Vite warning 当成失败处理。若看到 `Build gate failed because warnings were emitted`,先看 warning 原文,例如 chunk 体积超过 `vite.config.ts` / `apps/admin-web/vite.config.ts` 的 `chunkSizeWarningLimit`,不要先按 Rust 编译失败排查。 + 视觉小说负向扫描与验收门禁: ```bash @@ -149,6 +151,25 @@ Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分 生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git,不写入文档示例。 +50 HTTP req/s 首版压测优化口径: + +- `api-server` 生产模板默认 `GENARRATIVE_API_LISTEN_BACKLOG=1024`、`GENARRATIVE_API_WORKER_THREADS=4`;本地未设置 worker threads 时继续使用 Tokio 默认值。 +- `genarrative-api.service` 设置 `LimitNOFILE=65535`、`TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax` 和 `cat /proc/$(pidof api-server)/limits` 核对。 +- Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。 +- 作品列表 K6 脚本一次 iteration 默认请求两个公开接口,因此约 50 HTTP req/s 的目标命令使用 `SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works`。 +- 50 HTTP req/s 验收目标为 `http_req_failed < 1%`、`p95 < 2s`、`dropped_iterations = 0`,同时压测窗口内 Nginx 无新增 502。 + +OpenTelemetry 现阶段可选 OTLP traces / metrics / logs,但本地日志与 Nginx 文件日志仍保留: + +- 默认 `GENARRATIVE_OTEL_ENABLED=false`,未开启时 api-server 不依赖 Collector。 +- Collector 使用官方 `otelcol-contrib`,只监听 `127.0.0.1:4317/4318`;本地用 `npm run otel:debug` 启动 debug exporter,用 `npm run otel:rider` 转发到 Rider,再接 Jaeger、Tempo、Prometheus、Grafana 或托管平台。 +- api-server 开启时使用 `OTEL_SERVICE_NAME=genarrative-api`、`OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318`。 +- api-server 当前发 OTLP HTTP,`OTEL_EXPORTER_OTLP_ENDPOINT` 指向 Collector HTTP base endpoint;不要改到 gRPC `4317` 或 Rider 端口,Rider 由 Collector 通过 `RIDER_OTLP_GRPC_ENDPOINT` 转发。 +- 应用日志仍通过 `journalctl -u genarrative-api.service` 查看,Nginx 日志仍写文件;日志等级继续用 `GENARRATIVE_API_LOG` / `RUST_LOG` 控制,例如 `info,tower_http=info,spacetime_client=info`。 +- debug exporter / Rider 转发都会同时接收 traces、metrics 和 logs。 +- Rider 的 Logs 面板只展示 log event 自身字段,不会自动展开父 span 的全部 attributes;请求完成日志会直接带 `request_id`、`http.request.method`、`http.route`、`url.scheme`、`url.path`、`http.response.status_code`、`status_class`、`latency_ms` 和 `slow_request`,完整链路继续到 Traces 面板按 trace/span 查看。 +- 指标 label 只允许低基数字段:HTTP 使用 `method`、`route`、`status_class`,SpacetimeDB 调用使用 `procedure`、`status_class`;`request_id` 只进入 trace/log attribute,不进入 metric label。 + 常见外部服务变量: - `GENARRATIVE_SPACETIME_SERVER_URL` diff --git a/package-lock.json b/package-lock.json index 9008c606..b30a634e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1515,7 +1516,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -1528,7 +1528,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -1542,8 +1541,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@testing-library/react": { "version": "16.3.2", @@ -1606,8 +1604,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1650,7 +1647,8 @@ "version": "4.3.20", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/chai-subset": { "version": "1.3.6", @@ -1696,6 +1694,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1705,6 +1704,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -1796,6 +1796,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -2126,6 +2127,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2216,7 +2218,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -2338,6 +2339,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2629,7 +2631,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -2685,8 +2686,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/domexception": { "version": "4.0.0", @@ -2873,6 +2873,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3697,6 +3698,7 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, + "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -4096,7 +4098,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -4435,6 +4436,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true, "engines": { "node": ">=12" }, @@ -4486,6 +4488,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4619,6 +4622,7 @@ "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4627,6 +4631,7 @@ "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5074,6 +5079,7 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "devOptional": true, + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -5126,6 +5132,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5207,6 +5214,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -7027,6 +7035,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "peer": true, "requires": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -7835,15 +7844,13 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "peer": true + "dev": true }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "requires": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -7854,8 +7861,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true } } }, @@ -7891,8 +7897,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "@types/babel__core": { "version": "7.20.5", @@ -7935,7 +7940,8 @@ "version": "4.3.20", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true + "dev": true, + "peer": true }, "@types/chai-subset": { "version": "1.3.6", @@ -7978,6 +7984,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, + "peer": true, "requires": { "csstype": "^3.2.2" } @@ -7987,6 +7994,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, + "peer": true, "requires": {} }, "@types/semver": { @@ -8053,6 +8061,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -8263,7 +8272,8 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -8326,7 +8336,6 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "peer": true, "requires": { "dequal": "^2.0.3" } @@ -8396,6 +8405,7 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8605,8 +8615,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "peer": true + "dev": true }, "detect-libc": { "version": "2.1.2", @@ -8646,8 +8655,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true + "dev": true }, "domexception": { "version": "4.0.0", @@ -8782,6 +8790,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9360,6 +9369,7 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, + "peer": true, "requires": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -9566,8 +9576,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "peer": true + "dev": true }, "magic-string": { "version": "0.30.21", @@ -9813,7 +9822,8 @@ "picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true }, "pkg-types": { "version": "1.3.1", @@ -9843,6 +9853,7 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9926,12 +9937,14 @@ "react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==" + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "peer": true }, "react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "peer": true, "requires": { "scheduler": "^0.27.0" } @@ -10256,6 +10269,7 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "devOptional": true, + "peer": true, "requires": { "esbuild": "~0.27.0", "fsevents": "~2.3.3", @@ -10287,7 +10301,8 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true + "dev": true, + "peer": true }, "ufo": { "version": "1.6.3", @@ -10339,6 +10354,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "peer": true, "requires": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 325149e9..6ddc5166 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "dev:web": "node scripts/dev.mjs web", "dev:admin-web": "node scripts/dev.mjs admin-web", "dev:spacetime:logs": "node scripts/run-bash-script.mjs scripts/spacetime-logs-local.sh", + "otel:debug": "node scripts/run-otelcol.mjs debug", + "otel:rider": "node scripts/run-otelcol.mjs rider", "admin-web:build": "node scripts/admin-web-build.mjs build", "admin-web:typecheck": "node scripts/admin-web-build.mjs typecheck", "admin-web:preview": "npm --prefix apps/admin-web run preview --", diff --git a/scripts/loadtest/README.md b/scripts/loadtest/README.md index 0b406675..be788df5 100644 --- a/scripts/loadtest/README.md +++ b/scripts/loadtest/README.md @@ -113,6 +113,17 @@ $env:WORKS_DATA="data/works-list.local.json" npm run loadtest:k6:works -- --summary-trend-stats="avg,min,med,p(90),p(95),p(99),max" ``` +## 50 HTTP req/s 口径 + +`k6-works-list.js` 默认一次 iteration 会依次请求两个公开列表接口:`/api/runtime/puzzle/gallery` 和 `/api/runtime/custom-world-gallery`。因此目标约 50 HTTP req/s 时,`ramping-arrival-rate` 的 `PEAK_RPS` 应设置为 `25`。如果传入 `AUTH_TOKEN` 或把 `DETAIL_RATIO` 设为大于 0,每次 iteration 的请求数会增加,需要重新折算。 + +验收目标: + +- `http_req_failed < 1%` +- `http_req_duration p95 < 2000ms` +- `dropped_iterations = 0` +- 压测窗口内 Nginx 无新增 502 + ## Smoke ```bash @@ -151,17 +162,38 @@ BASE_URL=http://127.0.0.1:8787 \ WORKS_DATA=data/works-list.local.json \ SCENARIO=spike \ START_RPS=5 \ -PEAK_RPS=100 \ -HOLD=2m \ +PEAK_RPS=25 \ +HOLD=60s \ DETAIL_RATIO=0 \ npm run loadtest:k6:works ``` 默认阈值: -- `http_req_failed < 5%` +- `http_req_failed < 1%` - `http_req_duration p95 < 2000ms` -- `works_list_shape_error_rate < 5%` +- `dropped_iterations = 0` +- `works_list_shape_error_rate < 1%` + +PowerShell: + +```powershell +$env:BASE_URL="https://genarrative.world" +$env:WORKS_DATA="data/works-list.local.json" +$env:SCENARIO="spike" +$env:START_RPS="5" +$env:PEAK_RPS="25" +$env:HOLD="60s" +$env:END_RPS="5" +$env:DETAIL_RATIO="0" +npm run loadtest:k6:works -- --summary-trend-stats="avg,min,med,p(90),p(95),p(99),max" +``` + +线上 release 回归可使用同一组环境变量: + +```bash +SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works +``` ## 带登录态压测个人作品列表 @@ -197,6 +229,96 @@ npm run loadtest:k6:works - 如果个人作品列表返回 401,确认 `AUTH_TOKEN` 是当前 api-server 可识别的 access token。 - 如果详情全部 404,确认是否已向目标环境导入与 `WORKS_DATA` 一致的数据。 +## 压测窗口采集 + +Nginx upstream timing: + +```bash +sudo tail -f /var/log/nginx/genarrative.access.log +sudo tail -f /var/log/nginx/genarrative.error.log +``` + +api-server 与 SpacetimeDB 日志: + +```bash +sudo journalctl -u genarrative-api.service -f +sudo journalctl -u spacetimedb.service -f +``` + +api-server 的 OpenTelemetry 默认关闭。需要验证 OTLP traces / metrics / logs 时,先在服务器本机启动只监听 `127.0.0.1` 的 `otelcol-contrib` debug exporter: + +```bash +npm run otel:debug +``` + +如果要把本机数据转发给 Rider OpenTelemetry 面板,先在 Rider 的 OpenTelemetry 设置中启用固定 OTLP server port,例如 `17011`,再运行: + +```bash +RIDER_OTLP_GRPC_ENDPOINT=127.0.0.1:17011 npm run otel:rider +``` + +脚本会在 `.codex-temp/otelcol/` 生成临时 collector 配置,默认接收 api-server 发到 `http://127.0.0.1:4318` 的 OTLP HTTP 数据。需要改端口时可设置: + +- `OTELCOL_OTLP_HTTP_ENDPOINT`,默认 `127.0.0.1:4318` +- `OTELCOL_OTLP_GRPC_ENDPOINT`,默认 `127.0.0.1:4317` +- `RIDER_OTLP_GRPC_ENDPOINT`,默认 `127.0.0.1:17011` +- `OTELCOL_BIN`,默认 `otelcol-contrib` + +等价的 debug collector 配置如下: + +```yaml +receivers: + otlp: + protocols: + grpc: + endpoint: 127.0.0.1:4317 + http: + endpoint: 127.0.0.1:4318 + +exporters: + debug: + verbosity: detailed + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [debug] + metrics: + receivers: [otlp] + exporters: [debug] + logs: + receivers: [otlp] + exporters: [debug] +``` + +```bash +otelcol-contrib --config /etc/otelcol-contrib/genarrative-debug.yaml +``` + +然后在 `/etc/genarrative/api-server.env` 中打开: + +```env +GENARRATIVE_OTEL_ENABLED=true +OTEL_SERVICE_NAME=genarrative-api +OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 +``` + +注意 `api-server` 当前使用 OTLP HTTP exporter,`OTEL_EXPORTER_OTLP_ENDPOINT` 必须指向 Collector 的 HTTP base endpoint `http://127.0.0.1:4318`。不要把它改成 Collector gRPC 端口 `4317`,也不要直接指向 Rider 的 gRPC 端口;Rider 只由 `npm run otel:rider` 启动的 Collector 通过 `RIDER_OTLP_GRPC_ENDPOINT` 转发。 + +OTLP logs 是远端观测增量,不替代本地日志;api-server 日志仍看 `journalctl` / `logs/api-server/`,Nginx 日志仍看文件。日志等级继续用 `GENARRATIVE_API_LOG` / `RUST_LOG` 控制,例如 `info,tower_http=info,spacetime_client=info`。 + +Rider 的 Logs 面板展示的是 OTLP log event 自身字段,不会自动把父 span 的全部 attributes 摊平到每一条日志。请求完成日志会直接携带 `request_id`、`http.request.method`、`http.route`、`url.scheme`、`url.path`、`http.response.status_code`、`status_class`、`latency_ms` 和 `slow_request`;更完整的请求链路仍在 Traces 面板中按同一个 trace/span 关联查看。 + +线上回归辅助命令: + +```bash +systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax +cat /proc/$(pidof api-server)/limits +ss -ltnp | grep 8082 +curl -sS http://127.0.0.1:8082/healthz +``` + ## 验证命令 ```bash diff --git a/scripts/loadtest/k6-works-list.js b/scripts/loadtest/k6-works-list.js index 45e51a82..67d6abd0 100644 --- a/scripts/loadtest/k6-works-list.js +++ b/scripts/loadtest/k6-works-list.js @@ -56,20 +56,22 @@ const scenarioOptions = { scenarios: { spike: { executor: 'ramping-arrival-rate', + startRate: Number(__ENV.START_RPS || 5), preAllocatedVUs: Number(__ENV.PREALLOCATED_VUS || 50), maxVUs: Number(__ENV.MAX_VUS || 200), timeUnit: '1s', stages: [ - { target: Number(__ENV.START_RPS || 5), duration: __ENV.RAMP_UP || '30s' }, - { target: Number(__ENV.PEAK_RPS || 100), duration: __ENV.HOLD || '2m' }, + { target: Number(__ENV.PEAK_RPS || 25), duration: __ENV.RAMP_UP || '30s' }, + { target: Number(__ENV.PEAK_RPS || 25), duration: __ENV.HOLD || '2m' }, { target: Number(__ENV.END_RPS || 5), duration: __ENV.RAMP_DOWN || '30s' }, ], }, }, thresholds: { - http_req_failed: ['rate<0.05'], + http_req_failed: ['rate<0.01'], http_req_duration: ['p(95)<2000'], - works_list_shape_error_rate: ['rate<0.05'], + dropped_iterations: ['count==0'], + works_list_shape_error_rate: ['rate<0.01'], }, }, }; diff --git a/scripts/run-otelcol.mjs b/scripts/run-otelcol.mjs new file mode 100644 index 00000000..d070bfdc --- /dev/null +++ b/scripts/run-otelcol.mjs @@ -0,0 +1,119 @@ +import {spawn} from 'node:child_process'; +import {mkdirSync, writeFileSync} from 'node:fs'; +import path from 'node:path'; + +const [, , rawMode = 'debug', ...args] = process.argv; +const mode = rawMode.trim(); +const printConfigOnly = args.includes('--print-config'); + +const supportedModes = new Set(['debug', 'rider']); +if (!supportedModes.has(mode)) { + console.error('[otelcol] mode must be one of: debug, rider'); + process.exit(1); +} + +const otlpHttpEndpoint = readEnv('OTELCOL_OTLP_HTTP_ENDPOINT', '127.0.0.1:4318'); +const otlpGrpcEndpoint = readEnv('OTELCOL_OTLP_GRPC_ENDPOINT', '127.0.0.1:4317'); +const riderEndpoint = readEnv('RIDER_OTLP_GRPC_ENDPOINT', '127.0.0.1:17011'); +const debugVerbosity = readEnv('OTELCOL_DEBUG_VERBOSITY', 'detailed'); +const otelcolBin = readEnv('OTELCOL_BIN', 'otelcol-contrib'); + +const configText = buildConfig(mode); +const configDir = path.resolve('.codex-temp', 'otelcol'); +const configPath = path.join(configDir, `genarrative-${mode}.yaml`); +mkdirSync(configDir, {recursive: true}); +writeFileSync(configPath, configText, 'utf8'); + +console.log(`[otelcol] wrote ${configPath}`); +console.log(`[otelcol] receiving OTLP HTTP at http://${otlpHttpEndpoint}`); +console.log(`[otelcol] receiving OTLP gRPC at ${otlpGrpcEndpoint}`); +if (mode === 'rider') { + console.log(`[otelcol] forwarding traces/metrics/logs to Rider OTLP gRPC at ${riderEndpoint}`); +} +console.log( + '[otelcol] api-server env: GENARRATIVE_OTEL_ENABLED=true OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318' +); + +if (printConfigOnly) { + console.log(configText); + process.exit(0); +} + +const child = spawn(otelcolBin, ['--config', configPath], { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', +}); + +const stopChild = () => { + if (!child.killed) { + child.kill(); + } +}; + +for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) { + process.on(signal, () => { + stopChild(); + process.exit(130); + }); +} + +process.on('exit', stopChild); + +child.on('error', (error) => { + console.error(`[otelcol] failed to start ${otelcolBin}: ${error.message}`); + console.error('[otelcol] install otelcol-contrib and make sure it is on PATH, or set OTELCOL_BIN.'); + process.exit(1); +}); + +child.on('exit', (code, signal) => { + if (signal) { + console.error(`[otelcol] exited by signal: ${signal}`); + process.exit(1); + } + process.exit(code ?? 0); +}); + +function readEnv(key, fallback) { + const value = process.env[key]?.trim(); + return value ? value : fallback; +} + +function buildConfig(selectedMode) { + const exporters = + selectedMode === 'rider' + ? ` otlp/rider: + endpoint: ${riderEndpoint} + tls: + insecure: true + debug: + verbosity: ${debugVerbosity}` + : ` debug: + verbosity: ${debugVerbosity}`; + + const pipelineExporters = selectedMode === 'rider' ? '[otlp/rider, debug]' : '[debug]'; + + return `receivers: + otlp: + protocols: + grpc: + endpoint: ${otlpGrpcEndpoint} + http: + endpoint: ${otlpHttpEndpoint} + +exporters: +${exporters} + +service: + pipelines: + traces: + receivers: [otlp] + exporters: ${pipelineExporters} + metrics: + receivers: [otlp] + exporters: ${pipelineExporters} + logs: + receivers: [otlp] + exporters: ${pipelineExporters} +`; +} diff --git a/server-rs/Cargo.lock b/server-rs/Cargo.lock index 74415c0e..0eb3d4a3 100644 --- a/server-rs/Cargo.lock +++ b/server-rs/Cargo.lock @@ -105,6 +105,7 @@ dependencies = [ "module-square-hole", "module-story", "module-visual-novel", + "opentelemetry", "platform-agent", "platform-auth", "platform-llm", @@ -118,6 +119,7 @@ dependencies = [ "shared-contracts", "shared-kernel", "shared-logging", + "socket2 0.6.3", "spacetime-client", "time", "tokio", @@ -2070,6 +2072,90 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.0", + "opentelemetry", + "reqwest 0.12.28", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +dependencies = [ + "http 1.4.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.12.28", + "thiserror 2.0.18", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.4", + "thiserror 2.0.18", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2151,6 +2237,26 @@ dependencies = [ "indexmap 2.14.0", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2320,6 +2426,29 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "protobuf" version = "3.7.2" @@ -2622,6 +2751,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", "futures-util", "http 1.4.0", @@ -3036,6 +3166,12 @@ dependencies = [ name = "shared-logging" version = "0.1.0" dependencies = [ + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry_sdk", + "tracing", + "tracing-opentelemetry", "tracing-subscriber", ] @@ -3130,6 +3266,7 @@ dependencies = [ "module-square-hole", "module-story", "module-visual-novel", + "opentelemetry", "serde", "serde_json", "shared-contracts", @@ -3137,6 +3274,7 @@ dependencies = [ "spacetimedb-sdk", "time", "tokio", + "tracing", ] [[package]] @@ -3807,6 +3945,38 @@ version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project", + "sync_wrapper 1.0.2", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -3898,6 +4068,22 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" diff --git a/server-rs/Cargo.toml b/server-rs/Cargo.toml index 3a6ea980..6500ac2f 100644 --- a/server-rs/Cargo.toml +++ b/server-rs/Cargo.toml @@ -100,6 +100,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" serde_urlencoded = "0.7" sha2 = "0.10" +socket2 = "0.6" spacetimedb = "2.2.0" spacetimedb-sdk = "2.2.0" spacetimedb-lib = { version = "2.2.0", default-features = false } @@ -110,6 +111,11 @@ tokio-tungstenite = "0.27" tower = "0.5" tower-http = "0.6" tracing = "0.1" +opentelemetry = "0.31" +opentelemetry-appender-tracing = { version = "0.31", default-features = false, features = ["experimental_use_tracing_span_context"] } +opentelemetry-otlp = { version = "0.31", default-features = false, features = ["http-proto", "reqwest-blocking-client", "trace", "metrics", "logs"] } +opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics", "logs"] } +tracing-opentelemetry = { version = "0.32", default-features = false } tracing-subscriber = "0.3" url = "2" urlencoding = "2" diff --git a/server-rs/crates/api-server/Cargo.toml b/server-rs/crates/api-server/Cargo.toml index 90ab2c7b..92b07599 100644 --- a/server-rs/crates/api-server/Cargo.toml +++ b/server-rs/crates/api-server/Cargo.toml @@ -43,6 +43,7 @@ sha2 = { workspace = true } shared-contracts = { workspace = true, features = ["oss-contracts"] } shared-kernel = { workspace = true } shared-logging = { workspace = true } +socket2 = { workspace = true } spacetime-client = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net", "time"] } tokio-stream = { workspace = true } @@ -50,6 +51,7 @@ futures-util = { workspace = true } time = { workspace = true, features = ["formatting"] } tower-http = { workspace = true, features = ["trace"] } tracing = { workspace = true } +opentelemetry = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } uuid = { workspace = true, features = ["v4"] } diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 17956263..70cda406 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -11,7 +11,7 @@ use tower_http::{ classify::ServerErrorsFailureClass, trace::{DefaultOnRequest, TraceLayer}, }; -use tracing::{Level, Span, error, info, info_span, warn}; +use tracing::{Level, Span, error, info_span}; use crate::{ auth::{AuthenticatedAccessToken, require_bearer_auth}, @@ -22,6 +22,7 @@ use crate::{ response_headers::propagate_request_id_header, runtime_inventory::get_runtime_inventory_state, state::AppState, + telemetry::record_http_observability, tracking::record_route_tracking_event_after_success, vector_engine_audio_generation::{ create_background_music_task, create_sound_effect_task, @@ -42,8 +43,6 @@ use crate::{ // 统一由这里构造 Axum 路由树,后续再逐项挂接中间件与业务路由。 pub fn build_router(state: AppState) -> Router { - let slow_request_threshold_ms = state.config.slow_request_threshold_ms; - Router::new() .merge(modules::admin::router(state.clone())) .merge(modules::health::router(state.clone())) @@ -86,47 +85,55 @@ pub fn build_router(state: AppState) -> Router { state.clone(), record_api_tracking_after_success, )) + // HTTP 指标与请求完成日志放在 tracing span 内侧,日志事件可以继承当前 trace/span context。 + .layer(middleware::from_fn_with_state( + state.clone(), + record_http_observability, + )) // 当前阶段先统一挂接 HTTP tracing,后续 request_id、响应头与错误中间件继续在这里扩展。 .layer( TraceLayer::new_for_http() .make_span_with(|request: &Request| { let request_id = resolve_request_id(request).unwrap_or_else(|| "unknown".to_string()); + let route = crate::telemetry::observability_route(request.uri().path()); + let scheme = crate::telemetry::resolve_request_scheme(request.headers()); + let span_name = format!("{} {}", request.method(), route); info_span!( "http.request", + otel.kind = "server", + otel.name = %span_name, + otel.status_code = tracing::field::Empty, + http.response.status_code = tracing::field::Empty, method = %request.method(), - uri = %request.uri(), + http.request.method = %request.method(), + http.route = %route, + url.scheme = %scheme, + url.path = %request.uri().path(), request_id = %request_id, + status = tracing::field::Empty, + latency_ms = tracing::field::Empty, ) }) .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response( - move |response: &axum::response::Response, - latency: std::time::Duration, - span: &Span| { + |response: &axum::response::Response, + latency: std::time::Duration, + span: &Span| { let latency_ms = latency.as_millis().min(u64::MAX as u128) as u64; let status = response.status().as_u16(); - let slow_request = latency_ms >= slow_request_threshold_ms; span.record("status", status); + span.record("http.response.status_code", status); + span.record( + "otel.status_code", + if response.status().is_server_error() { + "ERROR" + } else { + "OK" + }, + ); span.record("latency_ms", latency_ms); - if slow_request { - warn!( - parent: span, - status, - latency_ms, - slow_request = true, - "http request completed slowly" - ); - } else { - info!( - parent: span, - status, - latency_ms, - slow_request = false, - "http request completed" - ); - } }, ) .on_failure( diff --git a/server-rs/crates/api-server/src/assets.rs b/server-rs/crates/api-server/src/assets.rs index 8b3afd6b..33d46ae5 100644 --- a/server-rs/crates/api-server/src/assets.rs +++ b/server-rs/crates/api-server/src/assets.rs @@ -752,10 +752,14 @@ mod tests { }; use hmac::{Hmac, Mac}; use http_body_util::BodyExt; + use platform_auth::{ + AccessTokenClaims, AccessTokenClaimsInput, AuthProvider, BindingStatus, sign_access_token, + }; use reqwest::{Method, multipart}; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use shared_kernel::new_uuid_simple_string; + use time::OffsetDateTime; use tower::ServiceExt; use crate::{app::build_router, config::AppConfig, state::AppState}; @@ -873,13 +877,17 @@ mod tests { ..AppConfig::default() }; - let app = build_router(AppState::new(config).expect("state should build")); + let state = AppState::new(config).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138120", "sess_assets_direct_upload").await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/direct-upload-tickets") + .header("authorization", format!("Bearer {token}")) .header("content-type", "application/json") .header("x-request-id", "req-oss-ticket") .header("x-genarrative-response-envelope", "1") @@ -1693,6 +1701,33 @@ mod tests { Ok(fields) } + async fn seed_authenticated_token( + state: &AppState, + phone_number: &str, + session_seed: &str, + ) -> String { + let user = state + .seed_test_phone_user_with_password(phone_number, "secret123") + .await; + let claims = AccessTokenClaims::from_input( + AccessTokenClaimsInput { + user_id: user.id.clone(), + session_id: state.seed_test_refresh_session_for_user(&user, session_seed), + provider: AuthProvider::Password, + roles: vec!["user".to_string()], + token_version: user.token_version, + phone_verified: true, + binding_status: BindingStatus::Active, + display_name: Some(user.display_name.clone()), + }, + state.auth_jwt_config(), + OffsetDateTime::now_utc(), + ) + .expect("claims should build"); + + sign_access_token(&claims, state.auth_jwt_config()).expect("token should sign") + } + fn build_object_url( config: &AppConfig, object_key: &str, diff --git a/server-rs/crates/api-server/src/config.rs b/server-rs/crates/api-server/src/config.rs index b8af62a4..955664de 100644 --- a/server-rs/crates/api-server/src/config.rs +++ b/server-rs/crates/api-server/src/config.rs @@ -20,7 +20,10 @@ pub(crate) const DEFAULT_VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS: u64 = 1_000_000 pub struct AppConfig { pub bind_host: String, pub bind_port: u16, + pub listen_backlog: i32, + pub worker_threads: Option, pub log_filter: String, + pub otel_enabled: bool, pub admin_username: Option, pub admin_password: Option, pub admin_token_ttl_seconds: u64, @@ -147,7 +150,10 @@ impl Default for AppConfig { Self { bind_host: "127.0.0.1".to_string(), bind_port: 3000, + listen_backlog: 1024, + worker_threads: None, log_filter: "info,tower_http=info".to_string(), + otel_enabled: false, admin_username: None, admin_password: None, admin_token_ttl_seconds: 4 * 60 * 60, @@ -301,6 +307,17 @@ impl AppConfig { { config.log_filter = log_filter; } + if let Some(listen_backlog) = + read_first_positive_i32_env(&["GENARRATIVE_API_LISTEN_BACKLOG"]) + { + config.listen_backlog = listen_backlog; + } + if let Some(worker_threads) = read_first_usize_env(&["GENARRATIVE_API_WORKER_THREADS"]) { + config.worker_threads = Some(worker_threads); + } + if let Some(otel_enabled) = read_first_bool_env(&["GENARRATIVE_OTEL_ENABLED"]) { + config.otel_enabled = otel_enabled; + } config.admin_username = read_first_non_empty_env(&["GENARRATIVE_ADMIN_USERNAME"]); config.admin_password = read_first_non_empty_env(&["GENARRATIVE_ADMIN_PASSWORD"]); @@ -881,6 +898,14 @@ fn read_first_positive_u32_env(keys: &[&str]) -> Option { }) } +fn read_first_positive_i32_env(keys: &[&str]) -> Option { + keys.iter().find_map(|key| { + env::var(key) + .ok() + .and_then(|value| parse_positive_i32(&value)) + }) +} + fn read_first_positive_u64_env(keys: &[&str]) -> Option { keys.iter().find_map(|key| { env::var(key) @@ -971,6 +996,15 @@ fn parse_positive_u32(raw: &str) -> Option { Some(value) } +fn parse_positive_i32(raw: &str) -> Option { + let value = raw.trim().parse::().ok()?; + if value <= 0 { + return None; + } + + Some(value) +} + fn parse_u32(raw: &str) -> Option { raw.trim().parse::().ok() } @@ -1151,6 +1185,34 @@ mod tests { } } + #[test] + fn from_env_reads_api_runtime_performance_settings() { + let _guard = ENV_LOCK + .get_or_init(|| Mutex::new(())) + .lock() + .expect("env lock should not poison"); + + unsafe { + std::env::remove_var("GENARRATIVE_API_LISTEN_BACKLOG"); + std::env::remove_var("GENARRATIVE_API_WORKER_THREADS"); + std::env::remove_var("GENARRATIVE_OTEL_ENABLED"); + std::env::set_var("GENARRATIVE_API_LISTEN_BACKLOG", "2048"); + std::env::set_var("GENARRATIVE_API_WORKER_THREADS", "6"); + std::env::set_var("GENARRATIVE_OTEL_ENABLED", "true"); + } + + let config = AppConfig::from_env(); + assert_eq!(config.listen_backlog, 2048); + assert_eq!(config.worker_threads, Some(6)); + assert!(config.otel_enabled); + + unsafe { + std::env::remove_var("GENARRATIVE_API_LISTEN_BACKLOG"); + std::env::remove_var("GENARRATIVE_API_WORKER_THREADS"); + std::env::remove_var("GENARRATIVE_OTEL_ENABLED"); + } + } + #[test] fn from_env_reads_wechat_pay_settings() { let _guard = ENV_LOCK diff --git a/server-rs/crates/api-server/src/main.rs b/server-rs/crates/api-server/src/main.rs index db6d0d28..d1f15cd9 100644 --- a/server-rs/crates/api-server/src/main.rs +++ b/server-rs/crates/api-server/src/main.rs @@ -75,6 +75,7 @@ mod square_hole_agent_turn; mod state; mod story_battles; mod story_sessions; +mod telemetry; mod tracking; mod vector_engine_audio_generation; mod visual_novel; @@ -85,8 +86,15 @@ mod wechat_provider; mod work_author; mod work_play_tracking; -use shared_logging::init_tracing; -use std::{collections::HashSet, env, fs, io, panic, thread, time::Duration}; +use shared_logging::{OtelConfig, init_tracing}; +use socket2::{Domain, Protocol, Socket, Type}; +use std::{ + collections::HashSet, + env, fs, io, + net::{SocketAddr, TcpListener as StdTcpListener}, + panic, thread, + time::Duration, +}; use tokio::net::TcpListener; use tokio::runtime::Builder as TokioRuntimeBuilder; use tokio::time::timeout; @@ -103,12 +111,18 @@ fn main() -> Result<(), io::Error> { .name("api-server-bootstrap".to_string()) .stack_size(API_SERVER_STARTUP_STACK_SIZE_BYTES) .spawn(|| { - TokioRuntimeBuilder::new_multi_thread() + load_local_env_files(); + let config = AppConfig::from_env(); + let mut runtime_builder = TokioRuntimeBuilder::new_multi_thread(); + runtime_builder .enable_all() .thread_name("api-server-worker") - .thread_stack_size(API_SERVER_STARTUP_STACK_SIZE_BYTES) - .build()? - .block_on(run_server()) + .thread_stack_size(API_SERVER_STARTUP_STACK_SIZE_BYTES); + if let Some(worker_threads) = config.worker_threads { + runtime_builder.worker_threads(worker_threads); + } + + runtime_builder.build()?.block_on(run_server(config)) })?; match server_thread.join() { @@ -117,28 +131,49 @@ fn main() -> Result<(), io::Error> { } } -async fn run_server() -> Result<(), io::Error> { - // 运行本地开发与联调时,优先从仓库根目录加载本地变量。 - // 只尊重外层 shell 先注入的变量;后续本地文件需要能覆盖前序本地文件。 - load_local_env_files(); - - // 统一先从配置对象读取监听地址,避免后续把环境变量读取散落到入口和路由层。 - let config = AppConfig::from_env(); - init_tracing(&config.log_filter)?; +async fn run_server(config: AppConfig) -> Result<(), io::Error> { + init_tracing( + &config.log_filter, + OtelConfig { + enabled: config.otel_enabled, + }, + )?; let bind_address = config.bind_socket_addr(); - let listener = TcpListener::bind(bind_address).await?; + let listen_backlog = config.listen_backlog; + let worker_threads = config.worker_threads; + let otel_enabled = config.otel_enabled; + let listener = build_tcp_listener(bind_address, listen_backlog)?; let state = restore_app_state_for_startup(config) .await .map_err(|error| std::io::Error::other(format!("初始化应用状态失败:{error}")))?; let router = build_router(state); - info!(%bind_address, "api-server 已完成 tracing 初始化并开始监听"); + info!( + %bind_address, + listen_backlog, + worker_threads = worker_threads.unwrap_or(0), + otel_enabled, + "api-server 已完成 tracing 初始化并开始监听" + ); axum::serve(listener, router).await } +fn build_tcp_listener( + bind_address: SocketAddr, + listen_backlog: i32, +) -> Result { + let domain = Domain::for_address(bind_address); + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?; + socket.set_reuse_address(true)?; + socket.set_nonblocking(true)?; + socket.bind(&bind_address.into())?; + socket.listen(listen_backlog)?; + TcpListener::from_std(StdTcpListener::from(socket)) +} + async fn restore_app_state_for_startup( config: AppConfig, ) -> Result { diff --git a/server-rs/crates/api-server/src/telemetry.rs b/server-rs/crates/api-server/src/telemetry.rs new file mode 100644 index 00000000..40347d8d --- /dev/null +++ b/server-rs/crates/api-server/src/telemetry.rs @@ -0,0 +1,182 @@ +use axum::{ + body::Body, + extract::State, + http::{HeaderMap, Request, Response}, + middleware::Next, +}; +use opentelemetry::{KeyValue, global, metrics::Counter}; +use tracing::{info, warn}; + +use crate::{request_context::resolve_request_id, state::AppState}; + +// 集中维护 api-server HTTP 观测,避免在 handler 中散落高基数字段或重复创建 instrument。 +pub async fn record_http_observability( + State(state): State, + request: Request, + next: Next, +) -> Response { + let method = request.method().as_str().to_string(); + let route = observability_route(request.uri().path()); + let scheme = resolve_request_scheme(request.headers()); + let path = request.uri().path().to_string(); + let request_id = resolve_request_id(&request).unwrap_or_else(|| "unknown".to_string()); + let base_labels = http_base_labels(method.clone(), route.clone()); + let metrics = http_metrics(); + metrics.in_flight.add(1, &base_labels); + let started_at = std::time::Instant::now(); + + let response = next.run(request).await; + let status = response.status().as_u16(); + let status_class = status_class(status); + let latency_ms = started_at.elapsed().as_millis().min(u64::MAX as u128) as u64; + let slow_request = latency_ms >= state.config.slow_request_threshold_ms; + let labels = http_response_labels(base_labels, status); + metrics.requests.add(1, &labels); + metrics + .duration + .record(started_at.elapsed().as_secs_f64(), &labels); + metrics.in_flight.add(-1, &labels[..2]); + + if slow_request { + warn!( + request_id = %request_id, + http.request.method = %method, + http.route = %route, + url.scheme = %scheme, + url.path = %path, + http.response.status_code = status, + status, + status_class, + latency_ms, + slow_request = true, + "http request completed slowly" + ); + } else { + info!( + request_id = %request_id, + http.request.method = %method, + http.route = %route, + url.scheme = %scheme, + url.path = %path, + http.response.status_code = status, + status, + status_class, + latency_ms, + slow_request = false, + "http request completed" + ); + } + + response +} + +struct HttpMetrics { + requests: Counter, + in_flight: opentelemetry::metrics::UpDownCounter, + duration: opentelemetry::metrics::Histogram, +} + +fn http_metrics() -> &'static HttpMetrics { + static METRICS: std::sync::OnceLock = std::sync::OnceLock::new(); + METRICS.get_or_init(|| { + let meter = global::meter("genarrative-api"); + HttpMetrics { + requests: meter + .u64_counter("genarrative.http.server.requests") + .with_description("HTTP request count grouped by route and status class") + .build(), + in_flight: meter + .i64_up_down_counter("http.server.active_requests") + .with_unit("{request}") + .with_description("Number of active HTTP server requests") + .build(), + duration: meter + .f64_histogram("http.server.request.duration") + .with_unit("s") + .with_description("Duration of HTTP server requests") + .build(), + } + }) +} + +fn http_base_labels(method: String, route: String) -> Vec { + vec![ + KeyValue::new("http.request.method", method), + KeyValue::new("http.route", route), + ] +} + +fn http_response_labels(mut labels: Vec, status: u16) -> Vec { + labels.push(KeyValue::new("status_class", status_class(status))); + labels +} + +fn status_class(status: u16) -> &'static str { + match status { + 100..=199 => "1xx", + 200..=299 => "2xx", + 300..=399 => "3xx", + 400..=499 => "4xx", + 500..=599 => "5xx", + _ => "unknown", + } +} + +pub(crate) fn observability_route(path: &str) -> String { + if path.starts_with("/api/runtime/puzzle/gallery") { + "/api/runtime/puzzle/gallery".to_string() + } else if path.starts_with("/api/runtime/custom-world-gallery") { + "/api/runtime/custom-world-gallery".to_string() + } else if path.starts_with("/admin/api/") { + "/admin/api/*".to_string() + } else if path.starts_with("/api/") { + "/api/*".to_string() + } else { + "other".to_string() + } +} + +pub(crate) fn resolve_request_scheme(headers: &HeaderMap) -> String { + headers + .get("x-forwarded-proto") + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.split(',').next()) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("http") + .to_string() +} + +#[cfg(test)] +mod tests { + use axum::http::{HeaderMap, HeaderValue}; + + use super::{observability_route, resolve_request_scheme}; + + #[test] + fn observability_route_keeps_metrics_labels_low_cardinality() { + assert_eq!( + observability_route("/api/runtime/puzzle/gallery?cursor=abc"), + "/api/runtime/puzzle/gallery" + ); + assert_eq!( + observability_route("/api/runtime/puzzle/runs/run-123/history"), + "/api/*" + ); + assert_eq!( + observability_route("/admin/api/debug/http"), + "/admin/api/*" + ); + } + + #[test] + fn resolve_request_scheme_uses_forwarded_proto_first_value() { + let mut headers = HeaderMap::new(); + headers.insert( + "x-forwarded-proto", + HeaderValue::from_static("https, http"), + ); + + assert_eq!(resolve_request_scheme(&headers), "https"); + } +} diff --git a/server-rs/crates/shared-logging/Cargo.toml b/server-rs/crates/shared-logging/Cargo.toml index 75235916..e91655e9 100644 --- a/server-rs/crates/shared-logging/Cargo.toml +++ b/server-rs/crates/shared-logging/Cargo.toml @@ -5,4 +5,10 @@ version.workspace = true license.workspace = true [dependencies] -tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +opentelemetry = { workspace = true } +opentelemetry-otlp = { workspace = true } +opentelemetry-appender-tracing = { workspace = true } +opentelemetry_sdk = { workspace = true } +tracing = { workspace = true } +tracing-opentelemetry = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "registry"] } diff --git a/server-rs/crates/shared-logging/src/lib.rs b/server-rs/crates/shared-logging/src/lib.rs index a810340e..ad77a6fb 100644 --- a/server-rs/crates/shared-logging/src/lib.rs +++ b/server-rs/crates/shared-logging/src/lib.rs @@ -1,6 +1,23 @@ use std::io; -use tracing_subscriber::{EnvFilter, fmt}; +use opentelemetry::{KeyValue, global, trace::TracerProvider}; +use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::{ + Resource, + logs::SdkLoggerProvider, + metrics::SdkMeterProvider, + trace::SdkTracerProvider, +}; +use tracing::warn; +use tracing_subscriber::{ + EnvFilter, Layer, filter::LevelFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt, +}; + +#[derive(Clone, Copy, Debug, Default)] +pub struct OtelConfig { + pub enabled: bool, +} // 统一解析工作区日志过滤器,优先环境变量,其次回落到调用方传入的默认值。 pub fn resolve_env_filter(default_filter: &str) -> EnvFilter { @@ -10,14 +27,196 @@ pub fn resolve_env_filter(default_filter: &str) -> EnvFilter { } // 统一初始化 tracing subscriber,避免各入口重复散落相同配置。 -pub fn init_tracing(default_filter: &str) -> Result<(), io::Error> { +pub fn init_tracing(default_filter: &str, otel_config: OtelConfig) -> Result<(), io::Error> { let env_filter = resolve_env_filter(default_filter); + let fmt_layer = fmt::layer().with_target(true).with_ansi(false).compact(); - fmt() - .with_env_filter(env_filter) - .with_target(true) - .with_ansi(false) - .compact() + if !otel_config.enabled { + return tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .try_init() + .map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}"))); + } + + let Some(otel) = build_otel_pipeline() else { + return tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .try_init() + .map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}"))); + }; + + tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .with( + tracing_opentelemetry::layer() + .with_tracer(otel.tracer_provider.tracer("genarrative-api")), + ) + .with( + OpenTelemetryTracingBridge::new(&otel.logger_provider).with_filter(LevelFilter::INFO), + ) .try_init() .map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}"))) } + +struct OtelPipeline { + tracer_provider: SdkTracerProvider, + _meter_provider: SdkMeterProvider, + logger_provider: SdkLoggerProvider, +} + +fn build_otel_pipeline() -> Option { + let resource = Resource::builder() + .with_service_name(read_env_or_default("OTEL_SERVICE_NAME", "genarrative-api")) + .with_attribute(KeyValue::new("service.namespace", "genarrative")) + .build(); + + let span_exporter = match opentelemetry_otlp::SpanExporter::builder() + .with_http() + .with_endpoint(resolve_otlp_http_signal_endpoint( + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "/v1/traces", + )) + .build() + { + Ok(exporter) => exporter, + Err(error) => { + warn!(%error, "OpenTelemetry span exporter 初始化失败,已回退为本地日志"); + return None; + } + }; + + let metric_exporter = match opentelemetry_otlp::MetricExporter::builder() + .with_http() + .with_endpoint(resolve_otlp_http_signal_endpoint( + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "/v1/metrics", + )) + .build() + { + Ok(exporter) => exporter, + Err(error) => { + warn!(%error, "OpenTelemetry metric exporter 初始化失败,已回退为本地日志"); + return None; + } + }; + + let log_exporter = match opentelemetry_otlp::LogExporter::builder() + .with_http() + .with_endpoint(resolve_otlp_http_signal_endpoint( + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + "/v1/logs", + )) + .build() + { + Ok(exporter) => exporter, + Err(error) => { + warn!(%error, "OpenTelemetry log exporter 初始化失败,已回退为本地日志"); + return None; + } + }; + + let tracer_provider = SdkTracerProvider::builder() + .with_resource(resource.clone()) + .with_batch_exporter(span_exporter) + .build(); + let meter_provider = SdkMeterProvider::builder() + .with_resource(resource) + .with_periodic_exporter(metric_exporter) + .build(); + let logger_provider = SdkLoggerProvider::builder() + .with_resource(Resource::builder() + .with_service_name(read_env_or_default("OTEL_SERVICE_NAME", "genarrative-api")) + .with_attribute(KeyValue::new("service.namespace", "genarrative")) + .build()) + .with_batch_exporter(log_exporter) + .build(); + + global::set_tracer_provider(tracer_provider.clone()); + global::set_meter_provider(meter_provider.clone()); + + Some(OtelPipeline { + tracer_provider, + _meter_provider: meter_provider, + logger_provider, + }) +} + +fn read_env_or_default(key: &str, default_value: &str) -> String { + std::env::var(key) + .ok() + .filter(|value| !value.trim().is_empty()) + .unwrap_or_else(|| default_value.to_string()) +} + +fn resolve_otlp_http_signal_endpoint(signal_key: &str, signal_path: &str) -> String { + if let Ok(value) = std::env::var(signal_key) + && !value.trim().is_empty() + { + return value; + } + + append_otlp_signal_path( + &read_env_or_default("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4318"), + signal_path, + ) +} + +fn append_otlp_signal_path(base_endpoint: &str, signal_path: &str) -> String { + let base_endpoint = base_endpoint.trim_end_matches('/'); + let signal_path = signal_path.trim_start_matches('/'); + format!("{base_endpoint}/{signal_path}") +} + +#[cfg(test)] +mod tests { + use std::sync::{Mutex, OnceLock}; + + use super::resolve_otlp_http_signal_endpoint; + + const OTEL_ENDPOINT_ENV_KEYS: [&str; 4] = [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + ]; + + fn env_lock() -> std::sync::MutexGuard<'static, ()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())).lock().unwrap() + } + + fn clear_otel_endpoint_env() { + unsafe { + for key in OTEL_ENDPOINT_ENV_KEYS { + std::env::remove_var(key); + } + } + } + + #[test] + fn generic_otlp_http_endpoint_expands_to_signal_paths() { + let _guard = env_lock(); + clear_otel_endpoint_env(); + unsafe { + std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4318"); + } + + assert_eq!( + resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "/v1/traces"), + "http://127.0.0.1:4318/v1/traces" + ); + assert_eq!( + resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "/v1/metrics"), + "http://127.0.0.1:4318/v1/metrics" + ); + assert_eq!( + resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "/v1/logs"), + "http://127.0.0.1:4318/v1/logs" + ); + + clear_otel_endpoint_env(); + } +} diff --git a/server-rs/crates/spacetime-client/Cargo.toml b/server-rs/crates/spacetime-client/Cargo.toml index 4499f545..734c0df9 100644 --- a/server-rs/crates/spacetime-client/Cargo.toml +++ b/server-rs/crates/spacetime-client/Cargo.toml @@ -27,3 +27,5 @@ shared-kernel = { workspace = true } spacetimedb-sdk = { workspace = true } time = { workspace = true } tokio = { workspace = true, features = ["rt", "sync", "time"] } +opentelemetry = { workspace = true } +tracing = { workspace = true } diff --git a/server-rs/crates/spacetime-client/src/ai.rs b/server-rs/crates/spacetime-client/src/ai.rs index 84a8041c..1ecbfd9d 100644 --- a/server-rs/crates/spacetime-client/src/ai.rs +++ b/server-rs/crates/spacetime-client/src/ai.rs @@ -8,7 +8,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("create_ai_task_and_return", move |connection, sender| { connection.procedures().create_ai_task_and_return_then( procedure_input, move |_, result| { @@ -28,7 +28,7 @@ impl SpacetimeClient { ) -> Result<(), SpacetimeClientError> { let reducer_input = input.into(); - self.call_reducer_after_connect(move |connection, sender| { + self.call_reducer_after_connect("start_ai_task", move |connection, sender| { let callback_sender = sender.clone(); if let Err(error) = connection @@ -52,7 +52,7 @@ impl SpacetimeClient { ) -> Result<(), SpacetimeClientError> { let reducer_input = input.into(); - self.call_reducer_after_connect(move |connection, sender| { + self.call_reducer_after_connect("start_ai_task_stage", move |connection, sender| { let callback_sender = sender.clone(); if let Err(error) = connection @@ -76,16 +76,19 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .append_ai_text_chunk_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_ai_task_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "append_ai_text_chunk_and_return", + move |connection, sender| { + connection + .procedures() + .append_ai_text_chunk_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_ai_task_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -95,7 +98,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("complete_ai_stage_and_return", move |connection, sender| { connection.procedures().complete_ai_stage_and_return_then( procedure_input, move |_, result| { @@ -115,16 +118,22 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .attach_ai_result_reference_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_ai_task_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "attach_ai_result_reference_and_return", + move |connection, sender| { + connection + .procedures() + .attach_ai_result_reference_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_ai_task_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -134,7 +143,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("complete_ai_task_and_return", move |connection, sender| { connection.procedures().complete_ai_task_and_return_then( procedure_input, move |_, result| { @@ -154,7 +163,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("fail_ai_task_and_return", move |connection, sender| { connection.procedures().fail_ai_task_and_return_then( procedure_input, move |_, result| { @@ -174,7 +183,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("cancel_ai_task_and_return", move |connection, sender| { connection.procedures().cancel_ai_task_and_return_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/assets.rs b/server-rs/crates/spacetime-client/src/assets.rs index ef0a910e..4cb2c2b7 100644 --- a/server-rs/crates/spacetime-client/src/assets.rs +++ b/server-rs/crates/spacetime-client/src/assets.rs @@ -7,17 +7,20 @@ impl SpacetimeClient { ) -> Result, SpacetimeClientError> { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection.procedures().list_asset_history_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_asset_history_list_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "list_asset_history_and_return", + move |connection, sender| { + connection.procedures().list_asset_history_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_asset_history_list_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -27,16 +30,19 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .confirm_asset_object_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "confirm_asset_object_and_return", + move |connection, sender| { + connection + .procedures() + .confirm_asset_object_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -46,16 +52,22 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .bind_asset_object_to_entity_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_entity_binding_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "bind_asset_object_to_entity_and_return", + move |connection, sender| { + connection + .procedures() + .bind_asset_object_to_entity_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_entity_binding_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/auth.rs b/server-rs/crates/spacetime-client/src/auth.rs index 438a2d69..e0f8faa1 100644 --- a/server-rs/crates/spacetime-client/src/auth.rs +++ b/server-rs/crates/spacetime-client/src/auth.rs @@ -4,23 +4,26 @@ impl SpacetimeClient { pub async fn export_auth_store_snapshot_from_tables( &self, ) -> Result { - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .export_auth_store_snapshot_from_tables_then(move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_auth_store_snapshot_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "export_auth_store_snapshot_from_tables", + move |connection, sender| { + connection + .procedures() + .export_auth_store_snapshot_from_tables_then(move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_auth_store_snapshot_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } pub async fn get_auth_store_snapshot( &self, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_auth_store_snapshot", move |connection, sender| { connection .procedures() .get_auth_store_snapshot_then(move |_, result| { @@ -43,7 +46,7 @@ impl SpacetimeClient { updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("upsert_auth_store_snapshot", move |connection, sender| { connection.procedures().upsert_auth_store_snapshot_then( procedure_input, move |_, result| { @@ -67,23 +70,26 @@ impl SpacetimeClient { updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .import_auth_store_snapshot_json_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_auth_store_snapshot_import_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "import_auth_store_snapshot_json", + move |connection, sender| { + connection + .procedures() + .import_auth_store_snapshot_json_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_auth_store_snapshot_import_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } pub async fn import_auth_store_snapshot( &self, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("import_auth_store_snapshot", move |connection, sender| { connection .procedures() .import_auth_store_snapshot_then(move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/bark_battle.rs b/server-rs/crates/spacetime-client/src/bark_battle.rs index 18985b15..19af1cb5 100644 --- a/server-rs/crates/spacetime-client/src/bark_battle.rs +++ b/server-rs/crates/spacetime-client/src/bark_battle.rs @@ -11,7 +11,7 @@ impl SpacetimeClient { &self, input: BarkBattleDraftCreateRecordInput, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("create_bark_battle_draft", move |connection, sender| { connection .procedures() .create_bark_battle_draft_then(input, move |_, result| { @@ -28,16 +28,19 @@ impl SpacetimeClient { &self, input: BarkBattleDraftConfigUpsertRecordInput, ) -> Result { - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .update_bark_battle_draft_config_then(input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_bark_battle_draft_config_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "update_bark_battle_draft_config", + move |connection, sender| { + connection + .procedures() + .update_bark_battle_draft_config_then(input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_bark_battle_draft_config_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -45,7 +48,7 @@ impl SpacetimeClient { &self, input: BarkBattleWorkPublishRecordInput, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_bark_battle_work", move |connection, sender| { connection .procedures() .publish_bark_battle_work_then(input, move |_, result| { @@ -67,16 +70,20 @@ impl SpacetimeClient { work_id, owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_bark_battle_runtime_config_then(input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_bark_battle_runtime_config_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_bark_battle_runtime_config", + move |connection, sender| { + connection.procedures().get_bark_battle_runtime_config_then( + input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_bark_battle_runtime_config_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -84,7 +91,7 @@ impl SpacetimeClient { &self, input: BarkBattleRunStartRecordInput, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_bark_battle_run", move |connection, sender| { connection .procedures() .start_bark_battle_run_then(input, move |_, result| { @@ -101,7 +108,7 @@ impl SpacetimeClient { &self, input: BarkBattleRunFinishRecordInput, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("finish_bark_battle_run", move |connection, sender| { connection .procedures() .finish_bark_battle_run_then(input, move |_, result| { @@ -123,7 +130,7 @@ impl SpacetimeClient { run_id, owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_bark_battle_run", move |connection, sender| { connection .procedures() .get_bark_battle_run_then(input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/big_fish.rs b/server-rs/crates/spacetime-client/src/big_fish.rs index 699cb5a6..38c976b4 100644 --- a/server-rs/crates/spacetime-client/src/big_fish.rs +++ b/server-rs/crates/spacetime-client/src/big_fish.rs @@ -23,7 +23,7 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("create_big_fish_session", move |connection, sender| { connection.procedures().create_big_fish_session_then( procedure_input, move |_, result| { @@ -47,7 +47,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_big_fish_session", move |connection, sender| { connection .procedures() .get_big_fish_session_then(procedure_input, move |_, result| { @@ -87,7 +87,7 @@ impl SpacetimeClient { &self, procedure_input: BigFishWorksListInput, ) -> Result, SpacetimeClientError> { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_big_fish_works", move |connection, sender| { let fallback_owner_user_id = if procedure_input.published_only { None } else { @@ -120,7 +120,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("delete_big_fish_work", move |connection, sender| { let fallback_owner_user_id = Some(procedure_input.owner_user_id.clone()); connection .procedures() @@ -152,7 +152,7 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("submit_big_fish_message", move |connection, sender| { connection.procedures().submit_big_fish_message_then( procedure_input, move |_, result| { @@ -182,16 +182,22 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_big_fish_agent_message_turn_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_big_fish_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "finalize_big_fish_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_big_fish_agent_message_turn_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_big_fish_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -206,7 +212,7 @@ impl SpacetimeClient { compiled_at_micros: input.compiled_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("compile_big_fish_draft", move |connection, sender| { connection.procedures().compile_big_fish_draft_then( procedure_input, move |_, result| { @@ -234,7 +240,7 @@ impl SpacetimeClient { generated_at_micros: input.generated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("generate_big_fish_asset", move |connection, sender| { connection.procedures().generate_big_fish_asset_then( procedure_input, move |_, result| { @@ -260,7 +266,7 @@ impl SpacetimeClient { published_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_big_fish_game", move |connection, sender| { connection.procedures().publish_big_fish_game_then( procedure_input, move |_, result| { @@ -285,7 +291,7 @@ impl SpacetimeClient { played_at_micros: input.reported_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("record_big_fish_play", move |connection, sender| { connection .procedures() .record_big_fish_play_then(procedure_input, move |_, result| { @@ -309,7 +315,7 @@ impl SpacetimeClient { started_at_micros: input.started_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_big_fish_run", move |connection, sender| { connection .procedures() .start_big_fish_run_then(procedure_input, move |_, result| { @@ -332,7 +338,7 @@ impl SpacetimeClient { liked_at_micros: input.liked_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("record_big_fish_like", move |connection, sender| { connection .procedures() .record_big_fish_like_then(procedure_input, move |_, result| { @@ -355,7 +361,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_big_fish_run", move |connection, sender| { connection .procedures() .get_big_fish_run_then(procedure_input, move |_, result| { @@ -380,7 +386,7 @@ impl SpacetimeClient { remixed_at_micros: input.remixed_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("remix_big_fish_work", move |connection, sender| { connection .procedures() .remix_big_fish_work_then(procedure_input, move |_, result| { @@ -405,7 +411,7 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("submit_big_fish_input", move |connection, sender| { connection.procedures().submit_big_fish_input_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/combat.rs b/server-rs/crates/spacetime-client/src/combat.rs index 80ae53ae..e8b4814e 100644 --- a/server-rs/crates/spacetime-client/src/combat.rs +++ b/server-rs/crates/spacetime-client/src/combat.rs @@ -9,17 +9,20 @@ impl SpacetimeClient { validate_battle_state_input(&input).map_err(SpacetimeClientError::validation_failed)?; let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection.procedures().create_battle_state_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_battle_state_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "create_battle_state_and_return", + move |connection, sender| { + connection.procedures().create_battle_state_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_battle_state_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -31,7 +34,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_battle_state", move |connection, sender| { connection .procedures() .get_battle_state_then(procedure_input, move |_, result| { @@ -52,16 +55,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)?; let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .resolve_combat_action_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_resolve_combat_action_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "resolve_combat_action_and_return", + move |connection, sender| { + connection + .procedures() + .resolve_combat_action_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_resolve_combat_action_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/custom_world.rs b/server-rs/crates/spacetime-client/src/custom_world.rs index 3cbeb56f..62b7be19 100644 --- a/server-rs/crates/spacetime-client/src/custom_world.rs +++ b/server-rs/crates/spacetime-client/src/custom_world.rs @@ -12,7 +12,7 @@ impl SpacetimeClient { ) -> Result, SpacetimeClientError> { let procedure_input = CustomWorldProfileListInput { owner_user_id }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_custom_world_profiles", move |connection, sender| { connection.procedures().list_custom_world_profiles_then( procedure_input, move |_, result| { @@ -36,16 +36,19 @@ impl SpacetimeClient { profile_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_custom_world_library_detail_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_detail_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_custom_world_library_detail", + move |connection, sender| { + connection + .procedures() + .get_custom_world_library_detail_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_detail_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -55,16 +58,22 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_custom_world_profile_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "upsert_custom_world_profile_and_return", + move |connection, sender| { + connection + .procedures() + .upsert_custom_world_profile_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -86,16 +95,22 @@ impl SpacetimeClient { published_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .publish_custom_world_profile_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "publish_custom_world_profile_and_return", + move |connection, sender| { + connection + .procedures() + .publish_custom_world_profile_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -113,19 +128,22 @@ impl SpacetimeClient { updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .unpublish_custom_world_profile_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "unpublish_custom_world_profile_and_return", + move |connection, sender| { + connection + .procedures() + .unpublish_custom_world_profile_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -141,32 +159,41 @@ impl SpacetimeClient { deleted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .delete_custom_world_profile_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_profile_list_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "delete_custom_world_profile_and_return", + move |connection, sender| { + connection + .procedures() + .delete_custom_world_profile_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_profile_list_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } pub async fn list_custom_world_gallery_entries( &self, ) -> Result, SpacetimeClientError> { - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .list_custom_world_gallery_entries_then(move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_gallery_list_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "list_custom_world_gallery_entries", + move |connection, sender| { + connection + .procedures() + .list_custom_world_gallery_entries_then(move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_gallery_list_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -180,16 +207,19 @@ impl SpacetimeClient { profile_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_custom_world_gallery_detail_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_custom_world_gallery_detail", + move |connection, sender| { + connection + .procedures() + .get_custom_world_gallery_detail_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -199,16 +229,22 @@ impl SpacetimeClient { ) -> Result { let procedure_input = CustomWorldGalleryDetailByCodeInput { public_work_code }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_custom_world_gallery_detail_by_code_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_custom_world_gallery_detail_by_code", + move |connection, sender| { + connection + .procedures() + .get_custom_world_gallery_detail_by_code_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -225,7 +261,7 @@ impl SpacetimeClient { remixed_at_micros: input.remixed_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("remix_custom_world_profile", move |connection, sender| { connection.procedures().remix_custom_world_profile_then( procedure_input, move |_, result| { @@ -249,16 +285,19 @@ impl SpacetimeClient { played_at_micros: input.played_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .record_custom_world_profile_play_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "record_custom_world_profile_play", + move |connection, sender| { + connection + .procedures() + .record_custom_world_profile_play_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -273,16 +312,19 @@ impl SpacetimeClient { liked_at_micros: input.liked_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .record_custom_world_profile_like_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_custom_world_library_mutation_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "record_custom_world_profile_like", + move |connection, sender| { + connection + .procedures() + .record_custom_world_profile_like_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_custom_world_library_mutation_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -292,7 +334,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_custom_world_world", move |connection, sender| { connection.procedures().publish_custom_world_world_then( procedure_input, move |_, result| { @@ -331,16 +373,19 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .create_custom_world_agent_session_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "create_custom_world_agent_session", + move |connection, sender| { + connection + .procedures() + .create_custom_world_agent_session_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -354,17 +399,20 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection.procedures().get_custom_world_agent_session_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "get_custom_world_agent_session", + move |connection, sender| { + connection.procedures().get_custom_world_agent_session_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -374,7 +422,7 @@ impl SpacetimeClient { ) -> Result, SpacetimeClientError> { let procedure_input = CustomWorldWorksListInput { owner_user_id }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_custom_world_works", move |connection, sender| { connection.procedures().list_custom_world_works_then( procedure_input, move |_, result| { @@ -398,16 +446,19 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .delete_custom_world_agent_session_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_works_list_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "delete_custom_world_agent_session", + move |connection, sender| { + connection + .procedures() + .delete_custom_world_agent_session_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_works_list_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -423,16 +474,19 @@ impl SpacetimeClient { card_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_custom_world_agent_card_detail_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_draft_card_detail_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_custom_world_agent_card_detail", + move |connection, sender| { + connection + .procedures() + .get_custom_world_agent_card_detail_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_draft_card_detail_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -449,16 +503,19 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .execute_custom_world_agent_action_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_action_execute_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "execute_custom_world_agent_action", + move |connection, sender| { + connection + .procedures() + .execute_custom_world_agent_action_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_action_execute_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -475,16 +532,19 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .submit_custom_world_agent_message_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_operation_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "submit_custom_world_agent_message", + move |connection, sender| { + connection + .procedures() + .submit_custom_world_agent_message_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_operation_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -521,19 +581,22 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_custom_world_agent_message_turn_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_operation_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "finalize_custom_world_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_custom_world_agent_message_turn_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_operation_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -556,19 +619,22 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_custom_world_agent_operation_progress_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_operation_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "upsert_custom_world_agent_operation_progress", + move |connection, sender| { + connection + .procedures() + .upsert_custom_world_agent_operation_progress_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_operation_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -584,16 +650,19 @@ impl SpacetimeClient { operation_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_custom_world_agent_operation_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_custom_world_agent_operation_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_custom_world_agent_operation", + move |connection, sender| { + connection + .procedures() + .get_custom_world_agent_operation_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_custom_world_agent_operation_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/inventory.rs b/server-rs/crates/spacetime-client/src/inventory.rs index 470c8c45..490a9a4b 100644 --- a/server-rs/crates/spacetime-client/src/inventory.rs +++ b/server-rs/crates/spacetime-client/src/inventory.rs @@ -11,7 +11,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_runtime_inventory_state", move |connection, sender| { connection.procedures().get_runtime_inventory_state_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/lib.rs b/server-rs/crates/spacetime-client/src/lib.rs index e222f6b2..cd09a684 100644 --- a/server-rs/crates/spacetime-client/src/lib.rs +++ b/server-rs/crates/spacetime-client/src/lib.rs @@ -3,6 +3,7 @@ pub mod module_bindings; mod mapper; +mod telemetry; use mapper::*; pub use mapper::{ AiResultReferenceRecord, AiTaskMutationRecord, AiTaskRecord, AiTaskStageRecord, @@ -142,6 +143,24 @@ use module_npc::{ NpcStanceProfile as DomainNpcStanceProfile, NpcStateSnapshot as DomainNpcStateSnapshot, ResolveNpcInteractionInput as DomainResolveNpcInteractionInput, }; +use module_puzzle::{ + PuzzleAgentMessageSnapshot as DomainPuzzleAgentMessageSnapshot, + PuzzleAgentSessionSnapshot as DomainPuzzleAgentSessionSnapshot, + PuzzleAgentSuggestedAction as DomainPuzzleAgentSuggestedAction, + PuzzleAnchorItem as DomainPuzzleAnchorItem, PuzzleAnchorPack as DomainPuzzleAnchorPack, + PuzzleBoardSnapshot as DomainPuzzleBoardSnapshot, + PuzzleCellPosition as DomainPuzzleCellPosition, + PuzzleCreatorIntent as DomainPuzzleCreatorIntent, PuzzleDraftLevel as DomainPuzzleDraftLevel, + PuzzleGeneratedImageCandidate as DomainPuzzleGeneratedImageCandidate, + PuzzleMergedGroupState as DomainPuzzleMergedGroupState, + PuzzlePieceState as DomainPuzzlePieceState, PuzzleResultDraft as DomainPuzzleResultDraft, + PuzzleResultPreviewBlocker as DomainPuzzleResultPreviewBlocker, + PuzzleResultPreviewEnvelope as DomainPuzzleResultPreviewEnvelope, + PuzzleResultPreviewFinding as DomainPuzzleResultPreviewFinding, + PuzzleRunSnapshot as DomainPuzzleRunSnapshot, + PuzzleRuntimeLevelSnapshot as DomainPuzzleRuntimeLevelSnapshot, + PuzzleWorkProfile as DomainPuzzleWorkProfile, +}; use module_runtime::{ AnalyticsMetricQueryResponse as DomainAnalyticsMetricQueryResponse, RuntimeBrowseHistoryRecord, RuntimePlatformTheme as DomainRuntimePlatformTheme, RuntimeProfileDashboardRecord, @@ -307,56 +326,72 @@ impl SpacetimeClient { async fn call_after_connect( &self, + procedure: &'static str, call: impl FnOnce(&DbConnection, ProcedureResultSender) + Send + 'static, ) -> Result where T: Send + 'static, { + let metrics_guard = telemetry::begin_procedure(procedure); let (sender, receiver) = oneshot::channel(); let result_sender = Arc::new(Mutex::new(Some(sender))); - let lease = self.acquire_connection().await?; - let final_result = if let Some(connection) = lease.connection.as_ref() { - call(&connection.connection, result_sender.clone()); - match timeout(self.config.procedure_timeout, receiver).await { - Ok(inner) => match inner { - Ok(value) => value, - Err(_) => Err(SpacetimeClientError::ConnectDropped), - }, - Err(_) => Err(Self::resolve_timeout_error(Some(connection))), + let final_result = match self.acquire_connection().await { + Ok(lease) => { + let result = if let Some(connection) = lease.connection.as_ref() { + call(&connection.connection, result_sender.clone()); + match timeout(self.config.procedure_timeout, receiver).await { + Ok(inner) => match inner { + Ok(value) => value, + Err(_) => Err(SpacetimeClientError::ConnectDropped), + }, + Err(_) => Err(Self::resolve_timeout_error(Some(connection))), + } + } else { + Err(SpacetimeClientError::Runtime( + "SpacetimeDB 连接租约缺少连接".to_string(), + )) + }; + self.release_connection(lease).await; + result } - } else { - Err(SpacetimeClientError::Runtime( - "SpacetimeDB 连接租约缺少连接".to_string(), - )) + Err(error) => Err(error), }; - self.release_connection(lease).await; + metrics_guard.finish(&final_result); final_result } async fn call_reducer_after_connect( &self, + procedure: &'static str, call: impl FnOnce(&DbConnection, ReducerResultSender) + Send + 'static, ) -> Result<(), SpacetimeClientError> { + let metrics_guard = telemetry::begin_procedure(procedure); let (sender, receiver) = oneshot::channel(); let result_sender = Arc::new(Mutex::new(Some(sender))); - let lease = self.acquire_connection().await?; - let final_result = if let Some(connection) = lease.connection.as_ref() { - call(&connection.connection, result_sender.clone()); - match timeout(self.config.procedure_timeout, receiver).await { - Ok(inner) => match inner { - Ok(value) => value, - Err(_) => Err(SpacetimeClientError::ConnectDropped), - }, - Err(_) => Err(Self::resolve_timeout_error(Some(connection))), + let final_result = match self.acquire_connection().await { + Ok(lease) => { + let result = if let Some(connection) = lease.connection.as_ref() { + call(&connection.connection, result_sender.clone()); + match timeout(self.config.procedure_timeout, receiver).await { + Ok(inner) => match inner { + Ok(value) => value, + Err(_) => Err(SpacetimeClientError::ConnectDropped), + }, + Err(_) => Err(Self::resolve_timeout_error(Some(connection))), + } + } else { + Err(SpacetimeClientError::Runtime( + "SpacetimeDB 连接租约缺少连接".to_string(), + )) + }; + self.release_connection(lease).await; + result } - } else { - Err(SpacetimeClientError::Runtime( - "SpacetimeDB 连接租约缺少连接".to_string(), - )) + Err(error) => Err(error), }; - self.release_connection(lease).await; + metrics_guard.finish(&final_result); final_result } @@ -488,7 +523,6 @@ impl SpacetimeClient { let mut subscriptions = Vec::new(); for query in [ "SELECT * FROM puzzle_gallery_view", - "SELECT * FROM public_work_play_daily_stat WHERE source_type = 'puzzle'", ] { let (sender, receiver) = oneshot::channel::>(); let applied_sender = Arc::new(Mutex::new(Some(sender))); diff --git a/server-rs/crates/spacetime-client/src/match3d.rs b/server-rs/crates/spacetime-client/src/match3d.rs index eb9efa7e..baaf7bb9 100644 --- a/server-rs/crates/spacetime-client/src/match3d.rs +++ b/server-rs/crates/spacetime-client/src/match3d.rs @@ -16,17 +16,20 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection.procedures().create_match_3_d_agent_session_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_match3d_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "create_match_3_d_agent_session", + move |connection, sender| { + connection.procedures().create_match_3_d_agent_session_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_match3d_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -40,7 +43,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_match_3_d_agent_session", move |connection, sender| { connection.procedures().get_match_3_d_agent_session_then( procedure_input, move |_, result| { @@ -66,17 +69,20 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection.procedures().submit_match_3_d_agent_message_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_match3d_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "submit_match_3_d_agent_message", + move |connection, sender| { + connection.procedures().submit_match_3_d_agent_message_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_match3d_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -96,16 +102,22 @@ impl SpacetimeClient { error_message: input.error_message, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_match_3_d_agent_message_turn_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_match3d_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "finalize_match_3_d_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_match_3_d_agent_message_turn_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_match3d_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -127,7 +139,7 @@ impl SpacetimeClient { generated_item_assets_json: input.generated_item_assets_json, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("compile_match_3_d_draft", move |connection, sender| { connection.procedures().compile_match_3_d_draft_then( procedure_input, move |_, result| { @@ -159,7 +171,7 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("update_match_3_d_work", move |connection, sender| { connection.procedures().update_match_3_d_work_then( procedure_input, move |_, result| { @@ -185,7 +197,7 @@ impl SpacetimeClient { published_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_match_3_d_work", move |connection, sender| { connection.procedures().publish_match_3_d_work_then( procedure_input, move |_, result| { @@ -225,7 +237,7 @@ impl SpacetimeClient { &self, procedure_input: Match3DWorksListInput, ) -> Result, SpacetimeClientError> { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_match_3_d_works", move |connection, sender| { connection .procedures() .list_match_3_d_works_then(procedure_input, move |_, result| { @@ -248,7 +260,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_match_3_d_work_detail", move |connection, sender| { connection.procedures().get_match_3_d_work_detail_then( procedure_input, move |_, result| { @@ -272,7 +284,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("delete_match_3_d_work", move |connection, sender| { connection.procedures().delete_match_3_d_work_then( procedure_input, move |_, result| { @@ -299,7 +311,7 @@ impl SpacetimeClient { item_type_count_override: input.item_type_count_override, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_match_3_d_run", move |connection, sender| { connection .procedures() .start_match_3_d_run_then(procedure_input, move |_, result| { @@ -327,7 +339,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_match_3_d_run", move |connection, sender| { connection .procedures() .get_match_3_d_run_then(procedure_input, move |_, result| { @@ -359,7 +371,7 @@ impl SpacetimeClient { clicked_at_ms: input.clicked_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("click_match_3_d_item", move |connection, sender| { connection .procedures() .click_match_3_d_item_then(procedure_input, move |_, result| { @@ -390,7 +402,7 @@ impl SpacetimeClient { stopped_at_ms: input.stopped_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("stop_match_3_d_run", move |connection, sender| { connection .procedures() .stop_match_3_d_run_then(procedure_input, move |_, result| { @@ -419,7 +431,7 @@ impl SpacetimeClient { restarted_at_ms: input.restarted_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("restart_match_3_d_run", move |connection, sender| { connection.procedures().restart_match_3_d_run_then( procedure_input, move |_, result| { @@ -448,7 +460,7 @@ impl SpacetimeClient { finished_at_ms: input.finished_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("finish_match_3_d_time_up", move |connection, sender| { connection.procedures().finish_match_3_d_time_up_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/mod.rs b/server-rs/crates/spacetime-client/src/module_bindings/mod.rs index 26fa2455..8e38203c 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/mod.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/mod.rs @@ -95,7 +95,6 @@ pub mod auth_store_snapshot_type; pub mod auth_store_snapshot_upsert_input_type; pub mod authorize_database_migration_operator_procedure; pub mod bark_battle_draft_config_row_type; -pub mod bark_battle_draft_config_snapshot_type; pub mod bark_battle_draft_config_table; pub mod bark_battle_draft_config_upsert_input_type; pub mod bark_battle_draft_create_input_type; @@ -108,10 +107,8 @@ pub mod bark_battle_published_config_row_type; pub mod bark_battle_published_config_table; pub mod bark_battle_run_finish_input_type; pub mod bark_battle_run_get_input_type; -pub mod bark_battle_run_snapshot_type; pub mod bark_battle_run_start_input_type; pub mod bark_battle_runtime_config_get_input_type; -pub mod bark_battle_runtime_config_snapshot_type; pub mod bark_battle_runtime_run_row_type; pub mod bark_battle_runtime_run_table; pub mod bark_battle_score_record_row_type; @@ -163,20 +160,16 @@ pub mod big_fish_run_get_input_type; pub mod big_fish_run_procedure_result_type; pub mod big_fish_run_start_input_type; pub mod big_fish_run_status_type; -pub mod big_fish_runtime_entity_snapshot_type; pub mod big_fish_runtime_params_type; pub mod big_fish_runtime_run_table; pub mod big_fish_runtime_run_type; -pub mod big_fish_runtime_snapshot_type; pub mod big_fish_session_create_input_type; pub mod big_fish_session_get_input_type; pub mod big_fish_session_procedure_result_type; pub mod big_fish_session_snapshot_type; -pub mod big_fish_vector_2_type; pub mod big_fish_work_delete_input_type; pub mod big_fish_work_like_record_input_type; pub mod big_fish_work_remix_input_type; -pub mod big_fish_work_summary_snapshot_type; pub mod big_fish_works_list_input_type; pub mod big_fish_works_procedure_result_type; pub mod bind_asset_object_to_entity_and_return_procedure; @@ -409,38 +402,30 @@ pub mod list_visual_novel_works_procedure; pub mod mark_profile_recharge_order_paid_and_return_procedure; pub mod match_3_d_agent_message_finalize_input_type; pub mod match_3_d_agent_message_row_type; -pub mod match_3_d_agent_message_snapshot_type; pub mod match_3_d_agent_message_submit_input_type; pub mod match_3_d_agent_message_table; pub mod match_3_d_agent_session_create_input_type; pub mod match_3_d_agent_session_get_input_type; pub mod match_3_d_agent_session_procedure_result_type; pub mod match_3_d_agent_session_row_type; -pub mod match_3_d_agent_session_snapshot_type; pub mod match_3_d_agent_session_table; pub mod match_3_d_click_item_procedure_result_type; -pub mod match_3_d_creator_config_snapshot_type; pub mod match_3_d_draft_compile_input_type; -pub mod match_3_d_draft_snapshot_type; -pub mod match_3_d_item_snapshot_type; pub mod match_3_d_run_click_input_type; pub mod match_3_d_run_get_input_type; pub mod match_3_d_run_procedure_result_type; pub mod match_3_d_run_restart_input_type; -pub mod match_3_d_run_snapshot_type; pub mod match_3_d_run_start_input_type; pub mod match_3_d_run_stop_input_type; pub mod match_3_d_run_time_up_input_type; pub mod match_3_d_runtime_run_row_type; pub mod match_3_d_runtime_run_table; -pub mod match_3_d_tray_slot_snapshot_type; pub mod match_3_d_work_delete_input_type; pub mod match_3_d_work_get_input_type; pub mod match_3_d_work_procedure_result_type; pub mod match_3_d_work_profile_row_type; pub mod match_3_d_work_profile_table; pub mod match_3_d_work_publish_input_type; -pub mod match_3_d_work_snapshot_type; pub mod match_3_d_work_update_input_type; pub mod match_3_d_works_list_input_type; pub mod match_3_d_works_procedure_result_type; @@ -514,58 +499,34 @@ pub mod puzzle_agent_message_finalize_input_type; pub mod puzzle_agent_message_kind_type; pub mod puzzle_agent_message_role_type; pub mod puzzle_agent_message_row_type; -pub mod puzzle_agent_message_snapshot_type; pub mod puzzle_agent_message_submit_input_type; pub mod puzzle_agent_message_table; pub mod puzzle_agent_session_create_input_type; pub mod puzzle_agent_session_get_input_type; pub mod puzzle_agent_session_procedure_result_type; pub mod puzzle_agent_session_row_type; -pub mod puzzle_agent_session_snapshot_type; pub mod puzzle_agent_session_table; pub mod puzzle_agent_stage_type; -pub mod puzzle_agent_suggested_action_type; -pub mod puzzle_anchor_item_type; -pub mod puzzle_anchor_pack_type; -pub mod puzzle_anchor_status_type; -pub mod puzzle_audio_asset_type; -pub mod puzzle_board_snapshot_type; -pub mod puzzle_cell_position_type; -pub mod puzzle_creator_intent_type; pub mod puzzle_draft_compile_input_type; -pub mod puzzle_draft_level_type; pub mod puzzle_event_kind_type; pub mod puzzle_event_table; pub mod puzzle_event_type; pub mod puzzle_form_draft_save_input_type; -pub mod puzzle_form_draft_type; pub mod puzzle_gallery_view_table; -pub mod puzzle_generated_image_candidate_type; pub mod puzzle_generated_images_save_input_type; pub mod puzzle_leaderboard_entry_row_type; pub mod puzzle_leaderboard_entry_table; -pub mod puzzle_leaderboard_entry_type; pub mod puzzle_leaderboard_submit_input_type; -pub mod puzzle_merged_group_state_type; -pub mod puzzle_piece_state_type; pub mod puzzle_publication_status_type; pub mod puzzle_publish_input_type; -pub mod puzzle_recommended_next_work_type; -pub mod puzzle_result_draft_type; -pub mod puzzle_result_preview_blocker_type; -pub mod puzzle_result_preview_envelope_type; -pub mod puzzle_result_preview_finding_type; pub mod puzzle_run_drag_input_type; pub mod puzzle_run_get_input_type; pub mod puzzle_run_next_level_input_type; pub mod puzzle_run_pause_input_type; pub mod puzzle_run_procedure_result_type; pub mod puzzle_run_prop_input_type; -pub mod puzzle_run_snapshot_type; pub mod puzzle_run_start_input_type; pub mod puzzle_run_swap_input_type; -pub mod puzzle_runtime_level_snapshot_type; -pub mod puzzle_runtime_level_status_type; pub mod puzzle_runtime_run_row_type; pub mod puzzle_runtime_run_table; pub mod puzzle_select_cover_image_input_type; @@ -577,7 +538,6 @@ pub mod puzzle_work_point_incentive_claim_input_type; pub mod puzzle_work_procedure_result_type; pub mod puzzle_work_profile_row_type; pub mod puzzle_work_profile_table; -pub mod puzzle_work_profile_type; pub mod puzzle_work_remix_input_type; pub mod puzzle_work_upsert_input_type; pub mod puzzle_works_list_input_type; @@ -769,41 +729,30 @@ pub mod seed_analytics_date_dimensions_reducer; pub mod select_puzzle_cover_image_procedure; pub mod square_hole_agent_message_finalize_input_type; pub mod square_hole_agent_message_row_type; -pub mod square_hole_agent_message_snapshot_type; pub mod square_hole_agent_message_submit_input_type; pub mod square_hole_agent_message_table; pub mod square_hole_agent_session_create_input_type; pub mod square_hole_agent_session_get_input_type; pub mod square_hole_agent_session_procedure_result_type; pub mod square_hole_agent_session_row_type; -pub mod square_hole_agent_session_snapshot_type; pub mod square_hole_agent_session_table; -pub mod square_hole_creator_config_snapshot_type; pub mod square_hole_draft_compile_input_type; -pub mod square_hole_draft_snapshot_type; -pub mod square_hole_drop_feedback_snapshot_type; pub mod square_hole_drop_shape_procedure_result_type; -pub mod square_hole_hole_option_snapshot_type; -pub mod square_hole_hole_snapshot_type; pub mod square_hole_run_drop_input_type; pub mod square_hole_run_get_input_type; pub mod square_hole_run_procedure_result_type; pub mod square_hole_run_restart_input_type; -pub mod square_hole_run_snapshot_type; pub mod square_hole_run_start_input_type; pub mod square_hole_run_stop_input_type; pub mod square_hole_run_time_up_input_type; pub mod square_hole_runtime_run_row_type; pub mod square_hole_runtime_run_table; -pub mod square_hole_shape_option_snapshot_type; -pub mod square_hole_shape_snapshot_type; pub mod square_hole_work_delete_input_type; pub mod square_hole_work_get_input_type; pub mod square_hole_work_procedure_result_type; pub mod square_hole_work_profile_row_type; pub mod square_hole_work_profile_table; pub mod square_hole_work_publish_input_type; -pub mod square_hole_work_snapshot_type; pub mod square_hole_work_update_input_type; pub mod square_hole_works_list_input_type; pub mod square_hole_works_procedure_result_type; @@ -880,31 +829,24 @@ pub mod user_browse_history_table; pub mod user_browse_history_type; pub mod visual_novel_agent_message_finalize_input_type; pub mod visual_novel_agent_message_row_type; -pub mod visual_novel_agent_message_snapshot_type; pub mod visual_novel_agent_message_submit_input_type; pub mod visual_novel_agent_message_table; pub mod visual_novel_agent_session_create_input_type; pub mod visual_novel_agent_session_get_input_type; pub mod visual_novel_agent_session_procedure_result_type; pub mod visual_novel_agent_session_row_type; -pub mod visual_novel_agent_session_snapshot_type; pub mod visual_novel_agent_session_table; pub mod visual_novel_history_procedure_result_type; -pub mod visual_novel_json_field_type; -pub mod visual_novel_json_value_type; pub mod visual_novel_run_get_input_type; pub mod visual_novel_run_procedure_result_type; -pub mod visual_novel_run_snapshot_type; pub mod visual_novel_run_snapshot_upsert_input_type; pub mod visual_novel_run_start_input_type; pub mod visual_novel_runtime_event_procedure_result_type; pub mod visual_novel_runtime_event_record_input_type; -pub mod visual_novel_runtime_event_snapshot_type; pub mod visual_novel_runtime_event_table; pub mod visual_novel_runtime_event_type; pub mod visual_novel_runtime_history_append_input_type; pub mod visual_novel_runtime_history_entry_row_type; -pub mod visual_novel_runtime_history_entry_snapshot_type; pub mod visual_novel_runtime_history_entry_table; pub mod visual_novel_runtime_history_list_input_type; pub mod visual_novel_runtime_run_row_type; @@ -916,7 +858,6 @@ pub mod visual_novel_work_procedure_result_type; pub mod visual_novel_work_profile_row_type; pub mod visual_novel_work_profile_table; pub mod visual_novel_work_publish_input_type; -pub mod visual_novel_work_snapshot_type; pub mod visual_novel_work_update_input_type; pub mod visual_novel_works_list_input_type; pub mod visual_novel_works_procedure_result_type; @@ -1010,7 +951,6 @@ pub use auth_store_snapshot_type::AuthStoreSnapshot; pub use auth_store_snapshot_upsert_input_type::AuthStoreSnapshotUpsertInput; pub use authorize_database_migration_operator_procedure::authorize_database_migration_operator; pub use bark_battle_draft_config_row_type::BarkBattleDraftConfigRow; -pub use bark_battle_draft_config_snapshot_type::BarkBattleDraftConfigSnapshot; pub use bark_battle_draft_config_table::*; pub use bark_battle_draft_config_upsert_input_type::BarkBattleDraftConfigUpsertInput; pub use bark_battle_draft_create_input_type::BarkBattleDraftCreateInput; @@ -1023,10 +963,8 @@ pub use bark_battle_published_config_row_type::BarkBattlePublishedConfigRow; pub use bark_battle_published_config_table::*; pub use bark_battle_run_finish_input_type::BarkBattleRunFinishInput; pub use bark_battle_run_get_input_type::BarkBattleRunGetInput; -pub use bark_battle_run_snapshot_type::BarkBattleRunSnapshot; pub use bark_battle_run_start_input_type::BarkBattleRunStartInput; pub use bark_battle_runtime_config_get_input_type::BarkBattleRuntimeConfigGetInput; -pub use bark_battle_runtime_config_snapshot_type::BarkBattleRuntimeConfigSnapshot; pub use bark_battle_runtime_run_row_type::BarkBattleRuntimeRunRow; pub use bark_battle_runtime_run_table::*; pub use bark_battle_score_record_row_type::BarkBattleScoreRecordRow; @@ -1078,20 +1016,16 @@ pub use big_fish_run_get_input_type::BigFishRunGetInput; pub use big_fish_run_procedure_result_type::BigFishRunProcedureResult; pub use big_fish_run_start_input_type::BigFishRunStartInput; pub use big_fish_run_status_type::BigFishRunStatus; -pub use big_fish_runtime_entity_snapshot_type::BigFishRuntimeEntitySnapshot; pub use big_fish_runtime_params_type::BigFishRuntimeParams; pub use big_fish_runtime_run_table::*; pub use big_fish_runtime_run_type::BigFishRuntimeRun; -pub use big_fish_runtime_snapshot_type::BigFishRuntimeSnapshot; pub use big_fish_session_create_input_type::BigFishSessionCreateInput; pub use big_fish_session_get_input_type::BigFishSessionGetInput; pub use big_fish_session_procedure_result_type::BigFishSessionProcedureResult; pub use big_fish_session_snapshot_type::BigFishSessionSnapshot; -pub use big_fish_vector_2_type::BigFishVector2; pub use big_fish_work_delete_input_type::BigFishWorkDeleteInput; pub use big_fish_work_like_record_input_type::BigFishWorkLikeRecordInput; pub use big_fish_work_remix_input_type::BigFishWorkRemixInput; -pub use big_fish_work_summary_snapshot_type::BigFishWorkSummarySnapshot; pub use big_fish_works_list_input_type::BigFishWorksListInput; pub use big_fish_works_procedure_result_type::BigFishWorksProcedureResult; pub use bind_asset_object_to_entity_and_return_procedure::bind_asset_object_to_entity_and_return; @@ -1324,38 +1258,30 @@ pub use list_visual_novel_works_procedure::list_visual_novel_works; pub use mark_profile_recharge_order_paid_and_return_procedure::mark_profile_recharge_order_paid_and_return; pub use match_3_d_agent_message_finalize_input_type::Match3DAgentMessageFinalizeInput; pub use match_3_d_agent_message_row_type::Match3DAgentMessageRow; -pub use match_3_d_agent_message_snapshot_type::Match3DAgentMessageSnapshot; pub use match_3_d_agent_message_submit_input_type::Match3DAgentMessageSubmitInput; pub use match_3_d_agent_message_table::*; pub use match_3_d_agent_session_create_input_type::Match3DAgentSessionCreateInput; pub use match_3_d_agent_session_get_input_type::Match3DAgentSessionGetInput; pub use match_3_d_agent_session_procedure_result_type::Match3DAgentSessionProcedureResult; pub use match_3_d_agent_session_row_type::Match3DAgentSessionRow; -pub use match_3_d_agent_session_snapshot_type::Match3DAgentSessionSnapshot; pub use match_3_d_agent_session_table::*; pub use match_3_d_click_item_procedure_result_type::Match3DClickItemProcedureResult; -pub use match_3_d_creator_config_snapshot_type::Match3DCreatorConfigSnapshot; pub use match_3_d_draft_compile_input_type::Match3DDraftCompileInput; -pub use match_3_d_draft_snapshot_type::Match3DDraftSnapshot; -pub use match_3_d_item_snapshot_type::Match3DItemSnapshot; pub use match_3_d_run_click_input_type::Match3DRunClickInput; pub use match_3_d_run_get_input_type::Match3DRunGetInput; pub use match_3_d_run_procedure_result_type::Match3DRunProcedureResult; pub use match_3_d_run_restart_input_type::Match3DRunRestartInput; -pub use match_3_d_run_snapshot_type::Match3DRunSnapshot; pub use match_3_d_run_start_input_type::Match3DRunStartInput; pub use match_3_d_run_stop_input_type::Match3DRunStopInput; pub use match_3_d_run_time_up_input_type::Match3DRunTimeUpInput; pub use match_3_d_runtime_run_row_type::Match3DRuntimeRunRow; pub use match_3_d_runtime_run_table::*; -pub use match_3_d_tray_slot_snapshot_type::Match3DTraySlotSnapshot; pub use match_3_d_work_delete_input_type::Match3DWorkDeleteInput; pub use match_3_d_work_get_input_type::Match3DWorkGetInput; pub use match_3_d_work_procedure_result_type::Match3DWorkProcedureResult; pub use match_3_d_work_profile_row_type::Match3DWorkProfileRow; pub use match_3_d_work_profile_table::*; pub use match_3_d_work_publish_input_type::Match3DWorkPublishInput; -pub use match_3_d_work_snapshot_type::Match3DWorkSnapshot; pub use match_3_d_work_update_input_type::Match3DWorkUpdateInput; pub use match_3_d_works_list_input_type::Match3DWorksListInput; pub use match_3_d_works_procedure_result_type::Match3DWorksProcedureResult; @@ -1429,58 +1355,34 @@ pub use puzzle_agent_message_finalize_input_type::PuzzleAgentMessageFinalizeInpu pub use puzzle_agent_message_kind_type::PuzzleAgentMessageKind; pub use puzzle_agent_message_role_type::PuzzleAgentMessageRole; pub use puzzle_agent_message_row_type::PuzzleAgentMessageRow; -pub use puzzle_agent_message_snapshot_type::PuzzleAgentMessageSnapshot; pub use puzzle_agent_message_submit_input_type::PuzzleAgentMessageSubmitInput; pub use puzzle_agent_message_table::*; pub use puzzle_agent_session_create_input_type::PuzzleAgentSessionCreateInput; pub use puzzle_agent_session_get_input_type::PuzzleAgentSessionGetInput; pub use puzzle_agent_session_procedure_result_type::PuzzleAgentSessionProcedureResult; pub use puzzle_agent_session_row_type::PuzzleAgentSessionRow; -pub use puzzle_agent_session_snapshot_type::PuzzleAgentSessionSnapshot; pub use puzzle_agent_session_table::*; pub use puzzle_agent_stage_type::PuzzleAgentStage; -pub use puzzle_agent_suggested_action_type::PuzzleAgentSuggestedAction; -pub use puzzle_anchor_item_type::PuzzleAnchorItem; -pub use puzzle_anchor_pack_type::PuzzleAnchorPack; -pub use puzzle_anchor_status_type::PuzzleAnchorStatus; -pub use puzzle_audio_asset_type::PuzzleAudioAsset; -pub use puzzle_board_snapshot_type::PuzzleBoardSnapshot; -pub use puzzle_cell_position_type::PuzzleCellPosition; -pub use puzzle_creator_intent_type::PuzzleCreatorIntent; pub use puzzle_draft_compile_input_type::PuzzleDraftCompileInput; -pub use puzzle_draft_level_type::PuzzleDraftLevel; pub use puzzle_event_kind_type::PuzzleEventKind; pub use puzzle_event_table::*; pub use puzzle_event_type::PuzzleEvent; pub use puzzle_form_draft_save_input_type::PuzzleFormDraftSaveInput; -pub use puzzle_form_draft_type::PuzzleFormDraft; pub use puzzle_gallery_view_table::*; -pub use puzzle_generated_image_candidate_type::PuzzleGeneratedImageCandidate; pub use puzzle_generated_images_save_input_type::PuzzleGeneratedImagesSaveInput; pub use puzzle_leaderboard_entry_row_type::PuzzleLeaderboardEntryRow; pub use puzzle_leaderboard_entry_table::*; -pub use puzzle_leaderboard_entry_type::PuzzleLeaderboardEntry; pub use puzzle_leaderboard_submit_input_type::PuzzleLeaderboardSubmitInput; -pub use puzzle_merged_group_state_type::PuzzleMergedGroupState; -pub use puzzle_piece_state_type::PuzzlePieceState; pub use puzzle_publication_status_type::PuzzlePublicationStatus; pub use puzzle_publish_input_type::PuzzlePublishInput; -pub use puzzle_recommended_next_work_type::PuzzleRecommendedNextWork; -pub use puzzle_result_draft_type::PuzzleResultDraft; -pub use puzzle_result_preview_blocker_type::PuzzleResultPreviewBlocker; -pub use puzzle_result_preview_envelope_type::PuzzleResultPreviewEnvelope; -pub use puzzle_result_preview_finding_type::PuzzleResultPreviewFinding; pub use puzzle_run_drag_input_type::PuzzleRunDragInput; pub use puzzle_run_get_input_type::PuzzleRunGetInput; pub use puzzle_run_next_level_input_type::PuzzleRunNextLevelInput; pub use puzzle_run_pause_input_type::PuzzleRunPauseInput; pub use puzzle_run_procedure_result_type::PuzzleRunProcedureResult; pub use puzzle_run_prop_input_type::PuzzleRunPropInput; -pub use puzzle_run_snapshot_type::PuzzleRunSnapshot; pub use puzzle_run_start_input_type::PuzzleRunStartInput; pub use puzzle_run_swap_input_type::PuzzleRunSwapInput; -pub use puzzle_runtime_level_snapshot_type::PuzzleRuntimeLevelSnapshot; -pub use puzzle_runtime_level_status_type::PuzzleRuntimeLevelStatus; pub use puzzle_runtime_run_row_type::PuzzleRuntimeRunRow; pub use puzzle_runtime_run_table::*; pub use puzzle_select_cover_image_input_type::PuzzleSelectCoverImageInput; @@ -1492,7 +1394,6 @@ pub use puzzle_work_point_incentive_claim_input_type::PuzzleWorkPointIncentiveCl pub use puzzle_work_procedure_result_type::PuzzleWorkProcedureResult; pub use puzzle_work_profile_row_type::PuzzleWorkProfileRow; pub use puzzle_work_profile_table::*; -pub use puzzle_work_profile_type::PuzzleWorkProfile; pub use puzzle_work_remix_input_type::PuzzleWorkRemixInput; pub use puzzle_work_upsert_input_type::PuzzleWorkUpsertInput; pub use puzzle_works_list_input_type::PuzzleWorksListInput; @@ -1684,41 +1585,30 @@ pub use seed_analytics_date_dimensions_reducer::seed_analytics_date_dimensions; pub use select_puzzle_cover_image_procedure::select_puzzle_cover_image; pub use square_hole_agent_message_finalize_input_type::SquareHoleAgentMessageFinalizeInput; pub use square_hole_agent_message_row_type::SquareHoleAgentMessageRow; -pub use square_hole_agent_message_snapshot_type::SquareHoleAgentMessageSnapshot; pub use square_hole_agent_message_submit_input_type::SquareHoleAgentMessageSubmitInput; pub use square_hole_agent_message_table::*; pub use square_hole_agent_session_create_input_type::SquareHoleAgentSessionCreateInput; pub use square_hole_agent_session_get_input_type::SquareHoleAgentSessionGetInput; pub use square_hole_agent_session_procedure_result_type::SquareHoleAgentSessionProcedureResult; pub use square_hole_agent_session_row_type::SquareHoleAgentSessionRow; -pub use square_hole_agent_session_snapshot_type::SquareHoleAgentSessionSnapshot; pub use square_hole_agent_session_table::*; -pub use square_hole_creator_config_snapshot_type::SquareHoleCreatorConfigSnapshot; pub use square_hole_draft_compile_input_type::SquareHoleDraftCompileInput; -pub use square_hole_draft_snapshot_type::SquareHoleDraftSnapshot; -pub use square_hole_drop_feedback_snapshot_type::SquareHoleDropFeedbackSnapshot; pub use square_hole_drop_shape_procedure_result_type::SquareHoleDropShapeProcedureResult; -pub use square_hole_hole_option_snapshot_type::SquareHoleHoleOptionSnapshot; -pub use square_hole_hole_snapshot_type::SquareHoleHoleSnapshot; pub use square_hole_run_drop_input_type::SquareHoleRunDropInput; pub use square_hole_run_get_input_type::SquareHoleRunGetInput; pub use square_hole_run_procedure_result_type::SquareHoleRunProcedureResult; pub use square_hole_run_restart_input_type::SquareHoleRunRestartInput; -pub use square_hole_run_snapshot_type::SquareHoleRunSnapshot; pub use square_hole_run_start_input_type::SquareHoleRunStartInput; pub use square_hole_run_stop_input_type::SquareHoleRunStopInput; pub use square_hole_run_time_up_input_type::SquareHoleRunTimeUpInput; pub use square_hole_runtime_run_row_type::SquareHoleRuntimeRunRow; pub use square_hole_runtime_run_table::*; -pub use square_hole_shape_option_snapshot_type::SquareHoleShapeOptionSnapshot; -pub use square_hole_shape_snapshot_type::SquareHoleShapeSnapshot; pub use square_hole_work_delete_input_type::SquareHoleWorkDeleteInput; pub use square_hole_work_get_input_type::SquareHoleWorkGetInput; pub use square_hole_work_procedure_result_type::SquareHoleWorkProcedureResult; pub use square_hole_work_profile_row_type::SquareHoleWorkProfileRow; pub use square_hole_work_profile_table::*; pub use square_hole_work_publish_input_type::SquareHoleWorkPublishInput; -pub use square_hole_work_snapshot_type::SquareHoleWorkSnapshot; pub use square_hole_work_update_input_type::SquareHoleWorkUpdateInput; pub use square_hole_works_list_input_type::SquareHoleWorksListInput; pub use square_hole_works_procedure_result_type::SquareHoleWorksProcedureResult; @@ -1795,31 +1685,24 @@ pub use user_browse_history_table::*; pub use user_browse_history_type::UserBrowseHistory; pub use visual_novel_agent_message_finalize_input_type::VisualNovelAgentMessageFinalizeInput; pub use visual_novel_agent_message_row_type::VisualNovelAgentMessageRow; -pub use visual_novel_agent_message_snapshot_type::VisualNovelAgentMessageSnapshot; pub use visual_novel_agent_message_submit_input_type::VisualNovelAgentMessageSubmitInput; pub use visual_novel_agent_message_table::*; pub use visual_novel_agent_session_create_input_type::VisualNovelAgentSessionCreateInput; pub use visual_novel_agent_session_get_input_type::VisualNovelAgentSessionGetInput; pub use visual_novel_agent_session_procedure_result_type::VisualNovelAgentSessionProcedureResult; pub use visual_novel_agent_session_row_type::VisualNovelAgentSessionRow; -pub use visual_novel_agent_session_snapshot_type::VisualNovelAgentSessionSnapshot; pub use visual_novel_agent_session_table::*; pub use visual_novel_history_procedure_result_type::VisualNovelHistoryProcedureResult; -pub use visual_novel_json_field_type::VisualNovelJsonField; -pub use visual_novel_json_value_type::VisualNovelJsonValue; pub use visual_novel_run_get_input_type::VisualNovelRunGetInput; pub use visual_novel_run_procedure_result_type::VisualNovelRunProcedureResult; -pub use visual_novel_run_snapshot_type::VisualNovelRunSnapshot; pub use visual_novel_run_snapshot_upsert_input_type::VisualNovelRunSnapshotUpsertInput; pub use visual_novel_run_start_input_type::VisualNovelRunStartInput; pub use visual_novel_runtime_event_procedure_result_type::VisualNovelRuntimeEventProcedureResult; pub use visual_novel_runtime_event_record_input_type::VisualNovelRuntimeEventRecordInput; -pub use visual_novel_runtime_event_snapshot_type::VisualNovelRuntimeEventSnapshot; pub use visual_novel_runtime_event_table::*; pub use visual_novel_runtime_event_type::VisualNovelRuntimeEvent; pub use visual_novel_runtime_history_append_input_type::VisualNovelRuntimeHistoryAppendInput; pub use visual_novel_runtime_history_entry_row_type::VisualNovelRuntimeHistoryEntryRow; -pub use visual_novel_runtime_history_entry_snapshot_type::VisualNovelRuntimeHistoryEntrySnapshot; pub use visual_novel_runtime_history_entry_table::*; pub use visual_novel_runtime_history_list_input_type::VisualNovelRuntimeHistoryListInput; pub use visual_novel_runtime_run_row_type::VisualNovelRuntimeRunRow; @@ -1831,7 +1714,6 @@ pub use visual_novel_work_procedure_result_type::VisualNovelWorkProcedureResult; pub use visual_novel_work_profile_row_type::VisualNovelWorkProfileRow; pub use visual_novel_work_profile_table::*; pub use visual_novel_work_publish_input_type::VisualNovelWorkPublishInput; -pub use visual_novel_work_snapshot_type::VisualNovelWorkSnapshot; pub use visual_novel_work_update_input_type::VisualNovelWorkUpdateInput; pub use visual_novel_works_list_input_type::VisualNovelWorksListInput; pub use visual_novel_works_procedure_result_type::VisualNovelWorksProcedureResult; @@ -2172,7 +2054,7 @@ pub struct DbUpdate { puzzle_agent_message: __sdk::TableUpdate, puzzle_agent_session: __sdk::TableUpdate, puzzle_event: __sdk::TableUpdate, - puzzle_gallery_view: __sdk::TableUpdate, + puzzle_gallery_view: __sdk::TableUpdate, puzzle_leaderboard_entry: __sdk::TableUpdate, puzzle_runtime_run: __sdk::TableUpdate, puzzle_work_profile: __sdk::TableUpdate, @@ -2966,7 +2848,7 @@ impl __sdk::DbUpdate for DbUpdate { &self.visual_novel_work_profile, ) .with_updates_by_pk(|row| &row.profile_id); - diff.puzzle_gallery_view = cache.apply_diff_to_table::( + diff.puzzle_gallery_view = cache.apply_diff_to_table::( "puzzle_gallery_view", &self.puzzle_gallery_view, ); @@ -3611,7 +3493,7 @@ pub struct AppliedDiff<'r> { puzzle_agent_message: __sdk::TableAppliedDiff<'r, PuzzleAgentMessageRow>, puzzle_agent_session: __sdk::TableAppliedDiff<'r, PuzzleAgentSessionRow>, puzzle_event: __sdk::TableAppliedDiff<'r, PuzzleEvent>, - puzzle_gallery_view: __sdk::TableAppliedDiff<'r, PuzzleWorkProfile>, + puzzle_gallery_view: __sdk::TableAppliedDiff<'r, PuzzleGalleryViewRow>, puzzle_leaderboard_entry: __sdk::TableAppliedDiff<'r, PuzzleLeaderboardEntryRow>, puzzle_runtime_run: __sdk::TableAppliedDiff<'r, PuzzleRuntimeRunRow>, puzzle_work_profile: __sdk::TableAppliedDiff<'r, PuzzleWorkProfileRow>, @@ -3959,7 +3841,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { &self.puzzle_event, event, ); - callbacks.invoke_table_row_callbacks::( + callbacks.invoke_table_row_callbacks::( "puzzle_gallery_view", &self.puzzle_gallery_view, event, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_gallery_view_table.rs b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_gallery_view_table.rs index 24857cee..229fa37a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/puzzle_gallery_view_table.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/puzzle_gallery_view_table.rs @@ -2,12 +2,246 @@ // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. #![allow(unused, clippy::all)] -use super::puzzle_anchor_pack_type::PuzzleAnchorPack; -use super::puzzle_draft_level_type::PuzzleDraftLevel; -use super::puzzle_publication_status_type::PuzzlePublicationStatus; -use super::puzzle_work_profile_type::PuzzleWorkProfile; use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, Copy, PartialEq, Debug)] +#[sats(crate = __lib)] +#[derive(Eq, Hash)] +pub enum PuzzleGalleryAnchorStatus { + Missing, + Inferred, + Confirmed, + Locked, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryAnchorItem { + pub key: String, + pub label: String, + pub value: String, + pub status: PuzzleGalleryAnchorStatus, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryAnchorPack { + pub theme_promise: PuzzleGalleryAnchorItem, + pub visual_subject: PuzzleGalleryAnchorItem, + pub visual_mood: PuzzleGalleryAnchorItem, + pub composition_hooks: PuzzleGalleryAnchorItem, + pub tags_and_forbidden: PuzzleGalleryAnchorItem, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryGeneratedImageCandidate { + pub candidate_id: String, + pub image_src: String, + pub asset_id: String, + pub prompt: String, + pub actual_prompt: Option, + pub source_type: String, + pub selected: bool, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryAudioAsset { + pub task_id: String, + pub provider: String, + pub asset_object_id: Option, + pub asset_kind: Option, + pub audio_src: String, + pub prompt: Option, + pub title: Option, + pub updated_at: Option, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryDraftLevel { + pub level_id: String, + pub level_name: String, + pub picture_description: String, + pub picture_reference: Option, + pub ui_background_prompt: Option, + pub ui_background_image_src: Option, + pub ui_background_image_object_key: Option, + pub background_music: Option, + pub candidates: Vec, + pub selected_candidate_id: Option, + pub cover_image_src: Option, + pub cover_asset_id: Option, + pub generation_status: String, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, Copy, PartialEq, Debug)] +#[sats(crate = __lib)] +#[derive(Eq, Hash)] +pub enum PuzzleGalleryPublicationStatus { + Draft, + Published, +} + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PuzzleGalleryViewRow { + pub work_id: String, + pub profile_id: String, + pub owner_user_id: String, + pub source_session_id: Option, + pub author_display_name: String, + pub work_title: String, + pub work_description: String, + pub level_name: String, + pub summary: String, + pub theme_tags: Vec, + pub cover_image_src: Option, + pub cover_asset_id: Option, + pub levels: Vec, + pub publication_status: PuzzleGalleryPublicationStatus, + pub updated_at_micros: i64, + pub published_at_micros: Option, + pub play_count: u32, + pub remix_count: u32, + pub like_count: u32, + pub recent_play_count_7d: u32, + pub point_incentive_total_half_points: u64, + pub point_incentive_claimed_points: u64, + pub publish_ready: bool, + pub anchor_pack: PuzzleGalleryAnchorPack, +} + +impl From for module_puzzle::PuzzleAnchorStatus { + fn from(status: PuzzleGalleryAnchorStatus) -> Self { + match status { + PuzzleGalleryAnchorStatus::Missing => Self::Missing, + PuzzleGalleryAnchorStatus::Inferred => Self::Inferred, + PuzzleGalleryAnchorStatus::Confirmed => Self::Confirmed, + PuzzleGalleryAnchorStatus::Locked => Self::Locked, + } + } +} + +impl From for module_puzzle::PuzzleAnchorItem { + fn from(item: PuzzleGalleryAnchorItem) -> Self { + Self { + key: item.key, + label: item.label, + value: item.value, + status: item.status.into(), + } + } +} + +impl From for module_puzzle::PuzzleAnchorPack { + fn from(pack: PuzzleGalleryAnchorPack) -> Self { + Self { + theme_promise: pack.theme_promise.into(), + visual_subject: pack.visual_subject.into(), + visual_mood: pack.visual_mood.into(), + composition_hooks: pack.composition_hooks.into(), + tags_and_forbidden: pack.tags_and_forbidden.into(), + } + } +} + +impl From + for module_puzzle::PuzzleGeneratedImageCandidate +{ + fn from(candidate: PuzzleGalleryGeneratedImageCandidate) -> Self { + Self { + candidate_id: candidate.candidate_id, + image_src: candidate.image_src, + asset_id: candidate.asset_id, + prompt: candidate.prompt, + actual_prompt: candidate.actual_prompt, + source_type: candidate.source_type, + selected: candidate.selected, + } + } +} + +impl From for module_puzzle::PuzzleAudioAsset { + fn from(asset: PuzzleGalleryAudioAsset) -> Self { + Self { + task_id: asset.task_id, + provider: asset.provider, + asset_object_id: asset.asset_object_id, + asset_kind: asset.asset_kind, + audio_src: asset.audio_src, + prompt: asset.prompt, + title: asset.title, + updated_at: asset.updated_at, + } + } +} + +impl From for module_puzzle::PuzzleDraftLevel { + fn from(level: PuzzleGalleryDraftLevel) -> Self { + Self { + level_id: level.level_id, + level_name: level.level_name, + picture_description: level.picture_description, + picture_reference: level.picture_reference, + ui_background_prompt: level.ui_background_prompt, + ui_background_image_src: level.ui_background_image_src, + ui_background_image_object_key: level.ui_background_image_object_key, + background_music: level.background_music.map(Into::into), + candidates: level.candidates.into_iter().map(Into::into).collect(), + selected_candidate_id: level.selected_candidate_id, + cover_image_src: level.cover_image_src, + cover_asset_id: level.cover_asset_id, + generation_status: level.generation_status, + } + } +} + +impl From for module_puzzle::PuzzlePublicationStatus { + fn from(status: PuzzleGalleryPublicationStatus) -> Self { + match status { + PuzzleGalleryPublicationStatus::Draft => Self::Draft, + PuzzleGalleryPublicationStatus::Published => Self::Published, + } + } +} + +impl From for module_puzzle::PuzzleWorkProfile { + fn from(row: PuzzleGalleryViewRow) -> Self { + Self { + work_id: row.work_id, + profile_id: row.profile_id, + owner_user_id: row.owner_user_id, + source_session_id: row.source_session_id, + author_display_name: row.author_display_name, + work_title: row.work_title, + work_description: row.work_description, + level_name: row.level_name, + summary: row.summary, + theme_tags: row.theme_tags, + cover_image_src: row.cover_image_src, + cover_asset_id: row.cover_asset_id, + levels: row.levels.into_iter().map(Into::into).collect(), + publication_status: row.publication_status.into(), + updated_at_micros: row.updated_at_micros, + published_at_micros: row.published_at_micros, + play_count: row.play_count, + remix_count: row.remix_count, + like_count: row.like_count, + recent_play_count_7d: row.recent_play_count_7d, + point_incentive_total_half_points: row.point_incentive_total_half_points, + point_incentive_claimed_points: row.point_incentive_claimed_points, + publish_ready: row.publish_ready, + anchor_pack: row.anchor_pack.into(), + } + } +} + +impl __sdk::InModule for PuzzleGalleryViewRow { + type Module = super::RemoteModule; +} + /// Table handle for the table `puzzle_gallery_view`. /// /// Obtain a handle from the [`PuzzleGalleryViewTableAccess::puzzle_gallery_view`] method on [`super::RemoteTables`], @@ -17,7 +251,7 @@ use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; /// but to directly chain method calls, /// like `ctx.db.puzzle_gallery_view().on_insert(...)`. pub struct PuzzleGalleryViewTableHandle<'ctx> { - imp: __sdk::TableHandle, + imp: __sdk::TableHandle, ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, } @@ -36,7 +270,7 @@ impl PuzzleGalleryViewTableAccess for super::RemoteTables { PuzzleGalleryViewTableHandle { imp: self .imp - .get_table::("puzzle_gallery_view"), + .get_table::("puzzle_gallery_view"), ctx: std::marker::PhantomData, } } @@ -46,13 +280,13 @@ pub struct PuzzleGalleryViewInsertCallbackId(__sdk::CallbackId); pub struct PuzzleGalleryViewDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PuzzleGalleryViewTableHandle<'ctx> { - type Row = PuzzleWorkProfile; + type Row = PuzzleGalleryViewRow; type EventContext = super::EventContext; fn count(&self) -> u64 { self.imp.count() } - fn iter(&self) -> impl Iterator + '_ { + fn iter(&self) -> impl Iterator + '_ { self.imp.iter() } @@ -85,32 +319,32 @@ impl<'ctx> __sdk::Table for PuzzleGalleryViewTableHandle<'ctx> { #[doc(hidden)] pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - let _table = client_cache.get_or_make_table::("puzzle_gallery_view"); + let _table = client_cache.get_or_make_table::("puzzle_gallery_view"); } #[doc(hidden)] pub(super) fn parse_table_update( raw_updates: __ws::v2::TableUpdate, -) -> __sdk::Result<__sdk::TableUpdate> { +) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") .with_cause(e) .into() }) } #[allow(non_camel_case_types)] -/// Extension trait for query builder access to the table `PuzzleWorkProfile`. +/// Extension trait for query builder access to the table `PuzzleGalleryViewRow`. /// /// Implemented for [`__sdk::QueryTableAccessor`]. pub trait puzzle_gallery_viewQueryTableAccess { #[allow(non_snake_case)] - /// Get a query builder for the table `PuzzleWorkProfile`. - fn puzzle_gallery_view(&self) -> __sdk::__query_builder::Table; + /// Get a query builder for the table `PuzzleGalleryViewRow`. + fn puzzle_gallery_view(&self) -> __sdk::__query_builder::Table; } impl puzzle_gallery_viewQueryTableAccess for __sdk::QueryTableAccessor { - fn puzzle_gallery_view(&self) -> __sdk::__query_builder::Table { + fn puzzle_gallery_view(&self) -> __sdk::__query_builder::Table { __sdk::__query_builder::Table::new("puzzle_gallery_view") } } diff --git a/server-rs/crates/spacetime-client/src/npc.rs b/server-rs/crates/spacetime-client/src/npc.rs index 77635c7b..61d33d2b 100644 --- a/server-rs/crates/spacetime-client/src/npc.rs +++ b/server-rs/crates/spacetime-client/src/npc.rs @@ -9,19 +9,22 @@ impl SpacetimeClient { validate_npc_battle_interaction_input(&input)?; let procedure_input = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .resolve_npc_battle_interaction_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_npc_battle_interaction_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "resolve_npc_battle_interaction_and_return", + move |connection, sender| { + connection + .procedures() + .resolve_npc_battle_interaction_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_npc_battle_interaction_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/puzzle.rs b/server-rs/crates/spacetime-client/src/puzzle.rs index 5426e756..1006e6f1 100644 --- a/server-rs/crates/spacetime-client/src/puzzle.rs +++ b/server-rs/crates/spacetime-client/src/puzzle.rs @@ -59,7 +59,7 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("create_puzzle_agent_session", move |connection, sender| { connection.procedures().create_puzzle_agent_session_then( procedure_input, move |_, result| { @@ -83,7 +83,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_puzzle_agent_session", move |connection, sender| { connection.procedures().get_puzzle_agent_session_then( procedure_input, move |_, result| { @@ -108,7 +108,7 @@ impl SpacetimeClient { saved_at_micros: input.saved_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("save_puzzle_form_draft", move |connection, sender| { connection.procedures().save_puzzle_form_draft_then( procedure_input, move |_, result| { @@ -134,7 +134,7 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("submit_puzzle_agent_message", move |connection, sender| { connection.procedures().submit_puzzle_agent_message_then( procedure_input, move |_, result| { @@ -164,16 +164,19 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_puzzle_agent_message_turn_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_puzzle_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "finalize_puzzle_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_puzzle_agent_message_turn_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -189,7 +192,7 @@ impl SpacetimeClient { compiled_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("compile_puzzle_agent_draft", move |connection, sender| { connection.procedures().compile_puzzle_agent_draft_then( procedure_input, move |_, result| { @@ -216,7 +219,7 @@ impl SpacetimeClient { saved_at_micros: input.saved_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("save_puzzle_generated_images", move |connection, sender| { connection.procedures().save_puzzle_generated_images_then( procedure_input, move |_, result| { @@ -245,7 +248,7 @@ impl SpacetimeClient { saved_at_micros: input.saved_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("save_puzzle_ui_background", move |connection, sender| { connection.procedures().save_puzzle_ui_background_then( procedure_input, move |_, result| { @@ -271,7 +274,7 @@ impl SpacetimeClient { selected_at_micros: input.selected_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("select_puzzle_cover_image", move |connection, sender| { connection.procedures().select_puzzle_cover_image_then( procedure_input, move |_, result| { @@ -304,7 +307,7 @@ impl SpacetimeClient { published_at_micros: input.published_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_puzzle_work", move |connection, sender| { connection .procedures() .publish_puzzle_work_then(procedure_input, move |_, result| { @@ -323,7 +326,7 @@ impl SpacetimeClient { ) -> Result, SpacetimeClientError> { let procedure_input = PuzzleWorksListInput { owner_user_id }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_puzzle_works", move |connection, sender| { connection .procedures() .list_puzzle_works_then(procedure_input, move |_, result| { @@ -342,7 +345,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = PuzzleWorkGetInput { profile_id }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_puzzle_work_detail", move |connection, sender| { connection.procedures().get_puzzle_work_detail_then( procedure_input, move |_, result| { @@ -374,7 +377,7 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("update_puzzle_work", move |connection, sender| { connection .procedures() .update_puzzle_work_then(procedure_input, move |_, result| { @@ -397,7 +400,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("delete_puzzle_work", move |connection, sender| { connection .procedures() .delete_puzzle_work_then(procedure_input, move |_, result| { @@ -420,16 +423,19 @@ impl SpacetimeClient { claimed_at_micros: input.claimed_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .claim_puzzle_work_point_incentive_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_puzzle_work_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "claim_puzzle_work_point_incentive", + move |connection, sender| { + connection + .procedures() + .claim_puzzle_work_point_incentive_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_puzzle_work_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -443,7 +449,7 @@ impl SpacetimeClient { Ok(items .into_iter() .map(|item| { - let mut record = map_puzzle_work_profile(item); + let mut record = map_puzzle_work_profile(item.into()); record.recent_play_count_7d = recent_play_counts .get(&record.profile_id) .copied() @@ -461,7 +467,7 @@ impl SpacetimeClient { ) -> Result { let procedure_input = PuzzleWorkGetInput { profile_id }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_puzzle_gallery_detail", move |connection, sender| { connection.procedures().get_puzzle_gallery_detail_then( procedure_input, move |_, result| { @@ -485,7 +491,7 @@ impl SpacetimeClient { liked_at_micros: input.liked_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("record_puzzle_work_like", move |connection, sender| { connection.procedures().record_puzzle_work_like_then( procedure_input, move |_, result| { @@ -514,7 +520,7 @@ impl SpacetimeClient { remixed_at_micros: input.remixed_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("remix_puzzle_work", move |connection, sender| { connection .procedures() .remix_puzzle_work_then(procedure_input, move |_, result| { @@ -539,7 +545,7 @@ impl SpacetimeClient { started_at_micros: input.started_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_puzzle_run", move |connection, sender| { connection .procedures() .start_puzzle_run_then(procedure_input, move |_, result| { @@ -562,7 +568,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_puzzle_run", move |connection, sender| { connection .procedures() .get_puzzle_run_then(procedure_input, move |_, result| { @@ -587,7 +593,7 @@ impl SpacetimeClient { swapped_at_micros: input.swapped_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("swap_puzzle_pieces", move |connection, sender| { connection .procedures() .swap_puzzle_pieces_then(procedure_input, move |_, result| { @@ -613,7 +619,7 @@ impl SpacetimeClient { dragged_at_micros: input.dragged_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("drag_puzzle_piece_or_group", move |connection, sender| { connection.procedures().drag_puzzle_piece_or_group_then( procedure_input, move |_, result| { @@ -638,7 +644,7 @@ impl SpacetimeClient { advanced_at_micros: input.advanced_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("advance_puzzle_next_level", move |connection, sender| { connection.procedures().advance_puzzle_next_level_then( procedure_input, move |_, result| { @@ -663,7 +669,7 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("update_puzzle_run_pause", move |connection, sender| { connection.procedures().update_puzzle_run_pause_then( procedure_input, move |_, result| { @@ -689,7 +695,7 @@ impl SpacetimeClient { spent_points: input.spent_points, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("use_puzzle_runtime_prop", move |connection, sender| { connection.procedures().use_puzzle_runtime_prop_then( procedure_input, move |_, result| { @@ -717,16 +723,19 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .submit_puzzle_leaderboard_entry_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_puzzle_run_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "submit_puzzle_leaderboard_entry", + move |connection, sender| { + connection + .procedures() + .submit_puzzle_leaderboard_entry_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_puzzle_run_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/runtime.rs b/server-rs/crates/spacetime-client/src/runtime.rs index 3ecd0d1f..a4b3aa29 100644 --- a/server-rs/crates/spacetime-client/src/runtime.rs +++ b/server-rs/crates/spacetime-client/src/runtime.rs @@ -4,7 +4,7 @@ impl SpacetimeClient { pub async fn get_creation_entry_config( &self, ) -> Result { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_creation_entry_config", move |connection, sender| { connection .procedures() .get_creation_entry_config_then(move |_, result| { @@ -22,16 +22,19 @@ impl SpacetimeClient { input: module_runtime::CreationEntryTypeAdminUpsertInput, ) -> Result { let procedure_input: CreationEntryTypeAdminUpsertInput = input.into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_creation_entry_type_config_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_creation_entry_config_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "upsert_creation_entry_type_config", + move |connection, sender| { + connection + .procedures() + .upsert_creation_entry_type_config_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_creation_entry_config_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -43,17 +46,20 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection.procedures().get_runtime_setting_or_default_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_setting_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "get_runtime_setting_or_default", + move |connection, sender| { + connection.procedures().get_runtime_setting_or_default_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_setting_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -65,7 +71,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_platform_browse_history", move |connection, sender| { connection.procedures().list_platform_browse_history_then( procedure_input, move |_, result| { @@ -87,7 +93,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_profile_dashboard", move |connection, sender| { connection.procedures().get_profile_dashboard_then( procedure_input, move |_, result| { @@ -109,7 +115,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_profile_wallet_ledger", move |connection, sender| { connection.procedures().list_profile_wallet_ledger_then( procedure_input, move |_, result| { @@ -131,19 +137,22 @@ impl SpacetimeClient { .map_err(|error| SpacetimeClientError::Runtime(error.to_string()))? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .grant_new_user_registration_wallet_reward_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_runtime_profile_wallet_adjustment_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "grant_new_user_registration_wallet_reward", + move |connection, sender| { + connection + .procedures() + .grant_new_user_registration_wallet_reward_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_runtime_profile_wallet_adjustment_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -163,19 +172,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .consume_profile_wallet_points_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_wallet_adjustment_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "consume_profile_wallet_points_and_return", + move |connection, sender| { + connection + .procedures() + .consume_profile_wallet_points_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_wallet_adjustment_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -195,16 +207,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .refund_profile_wallet_points_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_wallet_adjustment_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "refund_profile_wallet_points_and_return", + move |connection, sender| { + connection + .procedures() + .refund_profile_wallet_points_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_wallet_adjustment_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -216,7 +234,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_profile_recharge_center", move |connection, sender| { connection.procedures().get_profile_recharge_center_then( procedure_input, move |_, result| { @@ -252,19 +270,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .create_profile_recharge_order_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_recharge_order_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "create_profile_recharge_order_and_return", + move |connection, sender| { + connection + .procedures() + .create_profile_recharge_order_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_recharge_order_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -282,16 +303,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_profile_recharge_order_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_recharge_order_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_profile_recharge_order_and_return", + move |connection, sender| { + connection + .procedures() + .get_profile_recharge_order_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_recharge_order_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -315,19 +342,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .mark_profile_recharge_order_paid_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_recharge_order_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "mark_profile_recharge_order_paid_and_return", + move |connection, sender| { + connection + .procedures() + .mark_profile_recharge_order_paid_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_recharge_order_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -349,16 +379,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .submit_profile_feedback_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_feedback_submission_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "submit_profile_feedback_and_return", + move |connection, sender| { + connection + .procedures() + .submit_profile_feedback_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_feedback_submission_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -370,16 +403,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .get_profile_referral_invite_center_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_referral_invite_center_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "get_profile_referral_invite_center", + move |connection, sender| { + connection + .procedures() + .get_profile_referral_invite_center_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_referral_invite_center_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -394,16 +430,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .redeem_profile_referral_invite_code_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_referral_redeem_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "redeem_profile_referral_invite_code", + move |connection, sender| { + connection + .procedures() + .redeem_profile_referral_invite_code_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_referral_redeem_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -418,7 +457,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("redeem_profile_reward_code", move |connection, sender| { connection.procedures().redeem_profile_reward_code_then( procedure_input, move |_, result| { @@ -481,16 +520,19 @@ impl SpacetimeClient { occurred_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .record_tracking_event_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_tracking_event_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "record_tracking_event_and_return", + move |connection, sender| { + connection + .procedures() + .record_tracking_event_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_tracking_event_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -502,7 +544,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_profile_task_center", move |connection, sender| { connection.procedures().get_profile_task_center_then( procedure_input, move |_, result| { @@ -525,16 +567,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .claim_profile_task_reward_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_task_claim_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "claim_profile_task_reward_and_return", + move |connection, sender| { + connection + .procedures() + .claim_profile_task_reward_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_task_claim_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -550,7 +598,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("query_analytics_metric", move |connection, sender| { connection.procedures().query_analytics_metric_then( procedure_input, move |_, result| { @@ -572,16 +620,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_list_profile_task_configs_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_task_config_admin_list_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_list_profile_task_configs", + move |connection, sender| { + connection + .procedures() + .admin_list_profile_task_configs_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_task_config_admin_list_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -617,16 +668,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_upsert_profile_task_config_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_task_config_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_upsert_profile_task_config", + move |connection, sender| { + connection + .procedures() + .admin_upsert_profile_task_config_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_task_config_admin_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -644,16 +698,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_disable_profile_task_config_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_task_config_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_disable_profile_task_config", + move |connection, sender| { + connection + .procedures() + .admin_disable_profile_task_config_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_task_config_admin_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -666,16 +723,24 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_list_profile_recharge_products_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_recharge_product_admin_list_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_list_profile_recharge_products", + move |connection, sender| { + connection + .procedures() + .admin_list_profile_recharge_products_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then( + map_runtime_profile_recharge_product_admin_list_procedure_result, + ); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -716,16 +781,24 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_upsert_profile_recharge_product_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_recharge_product_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_upsert_profile_recharge_product", + move |connection, sender| { + connection + .procedures() + .admin_upsert_profile_recharge_product_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then( + map_runtime_profile_recharge_product_admin_procedure_result, + ); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -755,16 +828,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_upsert_profile_redeem_code_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_redeem_code_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_upsert_profile_redeem_code", + move |connection, sender| { + connection + .procedures() + .admin_upsert_profile_redeem_code_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_redeem_code_admin_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -776,16 +852,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_list_profile_redeem_codes_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_redeem_code_admin_list_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_list_profile_redeem_codes", + move |connection, sender| { + connection + .procedures() + .admin_list_profile_redeem_codes_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_redeem_code_admin_list_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -803,16 +882,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_disable_profile_redeem_code_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_redeem_code_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_disable_profile_redeem_code", + move |connection, sender| { + connection + .procedures() + .admin_disable_profile_redeem_code_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_redeem_code_admin_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -836,16 +918,19 @@ impl SpacetimeClient { .map_err(|error| SpacetimeClientError::Runtime(error.to_string()))? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_upsert_profile_invite_code_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_runtime_profile_invite_code_admin_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_upsert_profile_invite_code", + move |connection, sender| { + connection + .procedures() + .admin_upsert_profile_invite_code_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_runtime_profile_invite_code_admin_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -857,16 +942,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .admin_list_profile_invite_codes_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_invite_code_admin_list_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "admin_list_profile_invite_codes", + move |connection, sender| { + connection + .procedures() + .admin_list_profile_invite_codes_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_invite_code_admin_list_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -878,7 +966,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_profile_play_stats", move |connection, sender| { connection.procedures().get_profile_play_stats_then( procedure_input, move |_, result| { @@ -900,7 +988,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_runtime_snapshot", move |connection, sender| { connection .procedures() .get_runtime_snapshot_then(procedure_input, move |_, result| { @@ -933,16 +1021,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_runtime_snapshot_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_snapshot_required_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "upsert_runtime_snapshot_and_return", + move |connection, sender| { + connection + .procedures() + .upsert_runtime_snapshot_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_snapshot_required_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -954,16 +1045,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .delete_runtime_snapshot_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_snapshot_delete_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "delete_runtime_snapshot_and_return", + move |connection, sender| { + connection + .procedures() + .delete_runtime_snapshot_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_snapshot_delete_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -975,7 +1069,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_profile_save_archives", move |connection, sender| { connection.procedures().list_profile_save_archives_then( procedure_input, move |_, result| { @@ -999,16 +1093,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .resume_profile_save_archive_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_profile_save_archive_resume_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "resume_profile_save_archive_and_return", + move |connection, sender| { + connection + .procedures() + .resume_profile_save_archive_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_profile_save_archive_resume_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -1028,16 +1128,19 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_runtime_setting_and_return_then(procedure_input, move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_setting_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "upsert_runtime_setting_and_return", + move |connection, sender| { + connection + .procedures() + .upsert_runtime_setting_and_return_then(procedure_input, move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_setting_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -1052,19 +1155,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_platform_browse_history_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_browse_history_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "upsert_platform_browse_history_and_return", + move |connection, sender| { + connection + .procedures() + .upsert_platform_browse_history_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_browse_history_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -1076,19 +1182,22 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .clear_platform_browse_history_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_runtime_browse_history_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "clear_platform_browse_history_and_return", + move |connection, sender| { + connection + .procedures() + .clear_platform_browse_history_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_runtime_browse_history_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-client/src/square_hole.rs b/server-rs/crates/spacetime-client/src/square_hole.rs index f0ade205..ffeb616f 100644 --- a/server-rs/crates/spacetime-client/src/square_hole.rs +++ b/server-rs/crates/spacetime-client/src/square_hole.rs @@ -16,16 +16,19 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .create_square_hole_agent_session_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_square_hole_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "create_square_hole_agent_session", + move |connection, sender| { + connection + .procedures() + .create_square_hole_agent_session_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_square_hole_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -39,17 +42,20 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection.procedures().get_square_hole_agent_session_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_square_hole_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "get_square_hole_agent_session", + move |connection, sender| { + connection.procedures().get_square_hole_agent_session_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_square_hole_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -65,16 +71,19 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .submit_square_hole_agent_message_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_square_hole_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "submit_square_hole_agent_message", + move |connection, sender| { + connection + .procedures() + .submit_square_hole_agent_message_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_square_hole_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -94,16 +103,22 @@ impl SpacetimeClient { error_message: input.error_message, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_square_hole_agent_message_turn_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_square_hole_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "finalize_square_hole_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_square_hole_agent_message_turn_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_square_hole_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -123,7 +138,7 @@ impl SpacetimeClient { compiled_at_micros: input.compiled_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("compile_square_hole_draft", move |connection, sender| { connection.procedures().compile_square_hole_draft_then( procedure_input, move |_, result| { @@ -159,7 +174,7 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("update_square_hole_work", move |connection, sender| { connection.procedures().update_square_hole_work_then( procedure_input, move |_, result| { @@ -185,7 +200,7 @@ impl SpacetimeClient { published_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_square_hole_work", move |connection, sender| { connection.procedures().publish_square_hole_work_then( procedure_input, move |_, result| { @@ -225,7 +240,7 @@ impl SpacetimeClient { &self, procedure_input: SquareHoleWorksListInput, ) -> Result, SpacetimeClientError> { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_square_hole_works", move |connection, sender| { connection.procedures().list_square_hole_works_then( procedure_input, move |_, result| { @@ -249,7 +264,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_square_hole_work_detail", move |connection, sender| { connection.procedures().get_square_hole_work_detail_then( procedure_input, move |_, result| { @@ -273,7 +288,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("delete_square_hole_work", move |connection, sender| { connection.procedures().delete_square_hole_work_then( procedure_input, move |_, result| { @@ -298,7 +313,7 @@ impl SpacetimeClient { started_at_ms: input.started_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_square_hole_run", move |connection, sender| { connection.procedures().start_square_hole_run_then( procedure_input, move |_, result| { @@ -322,7 +337,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_square_hole_run", move |connection, sender| { connection .procedures() .get_square_hole_run_then(procedure_input, move |_, result| { @@ -349,7 +364,7 @@ impl SpacetimeClient { dropped_at_ms: input.dropped_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("drop_square_hole_shape", move |connection, sender| { connection.procedures().drop_square_hole_shape_then( procedure_input, move |_, result| { @@ -379,7 +394,7 @@ impl SpacetimeClient { stopped_at_ms: input.stopped_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("stop_square_hole_run", move |connection, sender| { connection .procedures() .stop_square_hole_run_then(procedure_input, move |_, result| { @@ -403,7 +418,7 @@ impl SpacetimeClient { restarted_at_ms: input.restarted_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("restart_square_hole_run", move |connection, sender| { connection.procedures().restart_square_hole_run_then( procedure_input, move |_, result| { @@ -427,7 +442,7 @@ impl SpacetimeClient { finished_at_ms: input.finished_at_ms, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("finish_square_hole_time_up", move |connection, sender| { connection.procedures().finish_square_hole_time_up_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/story.rs b/server-rs/crates/spacetime-client/src/story.rs index c04d02d1..d341385f 100644 --- a/server-rs/crates/spacetime-client/src/story.rs +++ b/server-rs/crates/spacetime-client/src/story.rs @@ -23,17 +23,20 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { - connection.procedures().begin_story_session_and_return_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(SpacetimeClientError::from_sdk_error) - .and_then(map_story_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "begin_story_session_and_return", + move |connection, sender| { + connection.procedures().begin_story_session_and_return_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(SpacetimeClientError::from_sdk_error) + .and_then(map_story_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -55,7 +58,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("continue_story_and_return", move |connection, sender| { connection.procedures().continue_story_and_return_then( procedure_input, move |_, result| { @@ -77,7 +80,7 @@ impl SpacetimeClient { .map_err(SpacetimeClientError::validation_failed)? .into(); - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_story_session_state", move |connection, sender| { connection.procedures().get_story_session_state_then( procedure_input, move |_, result| { diff --git a/server-rs/crates/spacetime-client/src/telemetry.rs b/server-rs/crates/spacetime-client/src/telemetry.rs new file mode 100644 index 00000000..9fdd9885 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/telemetry.rs @@ -0,0 +1,75 @@ +use std::time::Duration; + +use opentelemetry::{KeyValue, global, metrics::Counter}; + +use crate::SpacetimeClientError; + +// SpacetimeDB procedure 指标只使用 procedure / status_class 等低基数字段,避免把用户或作品 ID 写进指标标签。 +pub(crate) struct ProcedureMetricsGuard { + procedure: &'static str, + started_at: std::time::Instant, +} + +pub(crate) fn begin_procedure(procedure: &'static str) -> ProcedureMetricsGuard { + ProcedureMetricsGuard { + procedure, + started_at: std::time::Instant::now(), + } +} + +impl ProcedureMetricsGuard { + pub(crate) fn finish(&self, result: &Result) { + let duration = self.started_at.elapsed(); + record_procedure(self.procedure, duration, result.is_err()); + } +} + +struct SpacetimeMetrics { + calls: Counter, + errors: Counter, + duration_ms: opentelemetry::metrics::Histogram, +} + +fn spacetime_metrics() -> &'static SpacetimeMetrics { + static METRICS: std::sync::OnceLock = std::sync::OnceLock::new(); + METRICS.get_or_init(|| { + let meter = global::meter("genarrative-spacetime-client"); + SpacetimeMetrics { + calls: meter + .u64_counter("genarrative.spacetime.procedure.calls") + .with_description("SpacetimeDB procedure call count") + .build(), + errors: meter + .u64_counter("genarrative.spacetime.procedure.errors") + .with_description("SpacetimeDB procedure error count") + .build(), + duration_ms: meter + .f64_histogram("genarrative.spacetime.procedure.duration_ms") + .with_unit("ms") + .with_description("SpacetimeDB procedure duration in milliseconds") + .build(), + } + }) +} + +fn record_procedure( + procedure: &'static str, + duration: Duration, + failed: bool, +) { + let labels = vec![ + KeyValue::new("procedure", procedure), + KeyValue::new( + "status_class", + if failed { "error" } else { "ok" }, + ), + ]; + let metrics = spacetime_metrics(); + metrics.calls.add(1, &labels); + metrics + .duration_ms + .record(duration.as_secs_f64() * 1000.0, &labels); + if failed { + metrics.errors.add(1, &labels); + } +} diff --git a/server-rs/crates/spacetime-client/src/visual_novel.rs b/server-rs/crates/spacetime-client/src/visual_novel.rs index bbc8226a..bbf7a00f 100644 --- a/server-rs/crates/spacetime-client/src/visual_novel.rs +++ b/server-rs/crates/spacetime-client/src/visual_novel.rs @@ -28,16 +28,19 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .create_visual_novel_agent_session_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "create_visual_novel_agent_session", + move |connection, sender| { + connection + .procedures() + .create_visual_novel_agent_session_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -51,17 +54,20 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection.procedures().get_visual_novel_agent_session_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "get_visual_novel_agent_session", + move |connection, sender| { + connection.procedures().get_visual_novel_agent_session_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -77,16 +83,19 @@ impl SpacetimeClient { submitted_at_micros: input.submitted_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .submit_visual_novel_agent_message_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "submit_visual_novel_agent_message", + move |connection, sender| { + connection + .procedures() + .submit_visual_novel_agent_message_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -107,19 +116,22 @@ impl SpacetimeClient { error_message: input.error_message, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .finalize_visual_novel_agent_message_turn_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_agent_session_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "finalize_visual_novel_agent_message_turn", + move |connection, sender| { + connection + .procedures() + .finalize_visual_novel_agent_message_turn_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_agent_session_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -140,16 +152,19 @@ impl SpacetimeClient { compiled_at_micros: input.compiled_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .compile_visual_novel_work_profile_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_agent_session_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "compile_visual_novel_work_profile", + move |connection, sender| { + connection + .procedures() + .compile_visual_novel_work_profile_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_agent_session_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -170,7 +185,7 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("update_visual_novel_work", move |connection, sender| { connection.procedures().update_visual_novel_work_then( procedure_input, move |_, result| { @@ -196,7 +211,7 @@ impl SpacetimeClient { published_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("publish_visual_novel_work", move |connection, sender| { connection.procedures().publish_visual_novel_work_then( procedure_input, move |_, result| { @@ -236,7 +251,7 @@ impl SpacetimeClient { &self, procedure_input: VisualNovelWorksListInput, ) -> Result, SpacetimeClientError> { - self.call_after_connect(move |connection, sender| { + self.call_after_connect("list_visual_novel_works", move |connection, sender| { connection.procedures().list_visual_novel_works_then( procedure_input, move |_, result| { @@ -260,7 +275,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_visual_novel_work_detail", move |connection, sender| { connection.procedures().get_visual_novel_work_detail_then( procedure_input, move |_, result| { @@ -284,7 +299,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("delete_visual_novel_work", move |connection, sender| { connection.procedures().delete_visual_novel_work_then( procedure_input, move |_, result| { @@ -311,7 +326,7 @@ impl SpacetimeClient { started_at_micros: input.started_at_micros, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("start_visual_novel_run", move |connection, sender| { connection.procedures().start_visual_novel_run_then( procedure_input, move |_, result| { @@ -335,7 +350,7 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { + self.call_after_connect("get_visual_novel_run", move |connection, sender| { connection .procedures() .get_visual_novel_run_then(procedure_input, move |_, result| { @@ -367,16 +382,19 @@ impl SpacetimeClient { updated_at_micros: input.updated_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .upsert_visual_novel_run_snapshot_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_run_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "upsert_visual_novel_run_snapshot", + move |connection, sender| { + connection + .procedures() + .upsert_visual_novel_run_snapshot_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_run_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -397,19 +415,22 @@ impl SpacetimeClient { created_at_micros: input.created_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .append_visual_novel_runtime_history_entry_then( - procedure_input, - move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_history_procedure_result); - send_once(&sender, mapped); - }, - ); - }) + self.call_after_connect( + "append_visual_novel_runtime_history_entry", + move |connection, sender| { + connection + .procedures() + .append_visual_novel_runtime_history_entry_then( + procedure_input, + move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_history_procedure_result); + send_once(&sender, mapped); + }, + ); + }, + ) .await } @@ -423,16 +444,19 @@ impl SpacetimeClient { owner_user_id, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .list_visual_novel_runtime_history_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_history_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "list_visual_novel_runtime_history", + move |connection, sender| { + connection + .procedures() + .list_visual_novel_runtime_history_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_history_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } @@ -452,16 +476,19 @@ impl SpacetimeClient { occurred_at_micros: input.occurred_at_micros, }; - self.call_after_connect(move |connection, sender| { - connection - .procedures() - .record_visual_novel_runtime_event_then(procedure_input, move |_, result| { - let mapped = result - .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) - .and_then(map_visual_novel_runtime_event_procedure_result); - send_once(&sender, mapped); - }); - }) + self.call_after_connect( + "record_visual_novel_runtime_event", + move |connection, sender| { + connection + .procedures() + .record_visual_novel_runtime_event_then(procedure_input, move |_, result| { + let mapped = result + .map_err(|error| SpacetimeClientError::Procedure(error.to_string())) + .and_then(map_visual_novel_runtime_event_procedure_result); + send_once(&sender, mapped); + }); + }, + ) .await } } diff --git a/server-rs/crates/spacetime-module/src/puzzle.rs b/server-rs/crates/spacetime-module/src/puzzle.rs index 28f75c1e..512c3c6c 100644 --- a/server-rs/crates/spacetime-module/src/puzzle.rs +++ b/server-rs/crates/spacetime-module/src/puzzle.rs @@ -216,12 +216,12 @@ pub fn create_puzzle_agent_session( match ctx.try_with_tx(|tx| create_puzzle_agent_session_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -235,12 +235,12 @@ pub fn get_puzzle_agent_session( match ctx.try_with_tx(|tx| get_puzzle_agent_session_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -254,12 +254,12 @@ pub fn submit_puzzle_agent_message( match ctx.try_with_tx(|tx| submit_puzzle_agent_message_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -273,12 +273,12 @@ pub fn finalize_puzzle_agent_message_turn( match ctx.try_with_tx(|tx| finalize_puzzle_agent_message_turn_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -292,12 +292,12 @@ pub fn compile_puzzle_agent_draft( match ctx.try_with_tx(|tx| compile_puzzle_agent_draft_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -313,12 +313,12 @@ pub fn save_puzzle_form_draft( match ctx.try_with_tx(|tx| save_puzzle_form_draft_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -332,12 +332,12 @@ pub fn save_puzzle_generated_images( match ctx.try_with_tx(|tx| save_puzzle_generated_images_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -351,12 +351,12 @@ pub fn save_puzzle_ui_background( match ctx.try_with_tx(|tx| save_puzzle_ui_background_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -370,12 +370,12 @@ pub fn select_puzzle_cover_image( match ctx.try_with_tx(|tx| select_puzzle_cover_image_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -389,12 +389,12 @@ pub fn publish_puzzle_work( match ctx.try_with_tx(|tx| publish_puzzle_work_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -408,12 +408,12 @@ pub fn list_puzzle_works( match ctx.try_with_tx(|tx| list_puzzle_works_tx(tx, input.clone())) { Ok(items) => PuzzleWorksProcedureResult { ok: true, - items, + items_json: Some(serialize_json(&items)), error_message: None, }, Err(message) => PuzzleWorksProcedureResult { ok: false, - items: Vec::new(), + items_json: None, error_message: Some(message), }, } @@ -427,12 +427,12 @@ pub fn get_puzzle_work_detail( match ctx.try_with_tx(|tx| get_puzzle_work_detail_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -446,12 +446,12 @@ pub fn update_puzzle_work( match ctx.try_with_tx(|tx| update_puzzle_work_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -465,12 +465,12 @@ pub fn delete_puzzle_work( match ctx.try_with_tx(|tx| delete_puzzle_work_tx(tx, input.clone())) { Ok(items) => PuzzleWorksProcedureResult { ok: true, - items, + items_json: Some(serialize_json(&items)), error_message: None, }, Err(message) => PuzzleWorksProcedureResult { ok: false, - items: Vec::new(), + items_json: None, error_message: Some(message), }, } @@ -481,12 +481,12 @@ pub fn list_puzzle_gallery(ctx: &mut ProcedureContext) -> PuzzleWorksProcedureRe match ctx.try_with_tx(|tx| list_puzzle_gallery_tx(tx)) { Ok(items) => PuzzleWorksProcedureResult { ok: true, - items, + items_json: Some(serialize_json(&items)), error_message: None, }, Err(message) => PuzzleWorksProcedureResult { ok: false, - items: Vec::new(), + items_json: None, error_message: Some(message), }, } @@ -500,12 +500,12 @@ pub fn get_puzzle_gallery_detail( match ctx.try_with_tx(|tx| get_puzzle_gallery_detail_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -519,12 +519,12 @@ pub fn record_puzzle_work_like( match ctx.try_with_tx(|tx| record_puzzle_work_like_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -538,12 +538,12 @@ pub fn remix_puzzle_work( match ctx.try_with_tx(|tx| remix_puzzle_work_tx(tx, input.clone())) { Ok(session) => PuzzleAgentSessionProcedureResult { ok: true, - session: Some(session), + session_json: Some(serialize_json(&session)), error_message: None, }, Err(message) => PuzzleAgentSessionProcedureResult { ok: false, - session: None, + session_json: None, error_message: Some(message), }, } @@ -557,12 +557,12 @@ pub fn start_puzzle_run( match ctx.try_with_tx(|tx| start_puzzle_run_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -576,12 +576,12 @@ pub fn get_puzzle_run( match ctx.try_with_tx(|tx| get_puzzle_run_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -595,12 +595,12 @@ pub fn swap_puzzle_pieces( match ctx.try_with_tx(|tx| swap_puzzle_pieces_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -614,12 +614,12 @@ pub fn drag_puzzle_piece_or_group( match ctx.try_with_tx(|tx| drag_puzzle_piece_or_group_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -633,12 +633,12 @@ pub fn advance_puzzle_next_level( match ctx.try_with_tx(|tx| advance_puzzle_next_level_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -652,12 +652,12 @@ pub fn update_puzzle_run_pause( match ctx.try_with_tx(|tx| update_puzzle_run_pause_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -671,12 +671,12 @@ pub fn use_puzzle_runtime_prop( match ctx.try_with_tx(|tx| use_puzzle_runtime_prop_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } @@ -690,12 +690,12 @@ pub fn claim_puzzle_work_point_incentive( match ctx.try_with_tx(|tx| claim_puzzle_work_point_incentive_tx(tx, input.clone())) { Ok(item) => PuzzleWorkProcedureResult { ok: true, - item: Some(item), + item_json: Some(serialize_json(&item)), error_message: None, }, Err(message) => PuzzleWorkProcedureResult { ok: false, - item: None, + item_json: None, error_message: Some(message), }, } @@ -709,12 +709,12 @@ pub fn submit_puzzle_leaderboard_entry( match ctx.try_with_tx(|tx| submit_puzzle_leaderboard_entry_tx(tx, input.clone())) { Ok(run) => PuzzleRunProcedureResult { ok: true, - run: Some(run), + run_json: Some(serialize_json(&run)), error_message: None, }, Err(message) => PuzzleRunProcedureResult { ok: false, - run: None, + run_json: None, error_message: Some(message), }, } diff --git a/vite.config.ts b/vite.config.ts index c55a9803..ac0b1e69 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -58,7 +58,7 @@ export default defineConfig(({mode}) => { entries: ['index.html'], }, build: { - chunkSizeWarningLimit: 800, + chunkSizeWarningLimit: 1000, }, server: { // HMR is disabled in AI Studio via DISABLE_HMR env var.