@ic-reactor/mcp 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1041) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
  4. package/.github/actions/setup-rust-wasm/action.yml +25 -0
  5. package/.github/dependabot.yml +107 -0
  6. package/.github/workflows/auth-latest.yml +67 -0
  7. package/.github/workflows/b3pay-site.yml +49 -0
  8. package/.github/workflows/ci.yml +430 -0
  9. package/.github/workflows/codeql.yml +41 -0
  10. package/.github/workflows/dependency-review.yml +68 -0
  11. package/.github/workflows/docs-check.yml +98 -0
  12. package/.github/workflows/docs.yml +265 -0
  13. package/.github/workflows/e2e.yml +90 -0
  14. package/.github/workflows/examples-published.yml +97 -0
  15. package/.github/workflows/release.yml +326 -0
  16. package/.husky/pre-commit +12 -0
  17. package/.prettierignore +109 -0
  18. package/.prettierrc +6 -0
  19. package/AGENTS.md +103 -0
  20. package/CHANGELOG.md +3268 -0
  21. package/CLAUDE.md +93 -0
  22. package/CNAME +1 -0
  23. package/CODE_OF_CONDUCT.md +7 -0
  24. package/CONTRIBUTING.md +433 -0
  25. package/LICENSE +21 -0
  26. package/README.md +2 -2
  27. package/SECURITY.md +74 -0
  28. package/b3pay-module.json +194 -0
  29. package/docs/.remarkrc.json +10 -0
  30. package/docs/README.md +64 -0
  31. package/docs/astro.config.mjs +205 -0
  32. package/docs/favicon.svg +15 -0
  33. package/docs/package.json +39 -0
  34. package/docs/public/favicon.svg +15 -0
  35. package/docs/public/ic-reactor-social-card.png +0 -0
  36. package/docs/scripts/b3pay-static.mjs +22 -0
  37. package/docs/scripts/check-example-docs.mjs +214 -0
  38. package/docs/scripts/check-links.mjs +212 -0
  39. package/docs/scripts/fix-dist-links.js +125 -0
  40. package/docs/scripts/fix-frontmatter.js +71 -0
  41. package/docs/scripts/remove-redundant-h1.cjs +48 -0
  42. package/docs/scripts/rewrite-typedoc-links.js +110 -0
  43. package/docs/scripts/split-index.js +69 -0
  44. package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
  45. package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
  46. package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
  47. package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
  48. package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
  49. package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
  50. package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
  51. package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
  52. package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
  53. package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
  54. package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
  55. package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
  56. package/docs/src/components/LibraryCards.astro +172 -0
  57. package/docs/src/components/LibraryIcon.astro +28 -0
  58. package/docs/src/components/SiteTitle.astro +241 -0
  59. package/docs/src/content/docs/404.mdx +19 -0
  60. package/docs/src/content/docs/build/agent-tools.mdx +593 -0
  61. package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
  62. package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
  63. package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
  64. package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
  65. package/docs/src/content/docs/examples/index.mdx +38 -0
  66. package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
  67. package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
  68. package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
  69. package/docs/src/content/docs/guides/auth.mdx +1181 -0
  70. package/docs/src/content/docs/guides/client.mdx +551 -0
  71. package/docs/src/content/docs/guides/deploy.mdx +180 -0
  72. package/docs/src/content/docs/guides/errors.mdx +475 -0
  73. package/docs/src/content/docs/guides/getting-started.mdx +620 -0
  74. package/docs/src/content/docs/guides/reads.mdx +586 -0
  75. package/docs/src/content/docs/guides/ssr.mdx +529 -0
  76. package/docs/src/content/docs/guides/testing.mdx +401 -0
  77. package/docs/src/content/docs/guides/values.mdx +261 -0
  78. package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
  79. package/docs/src/content/docs/guides/writes.mdx +572 -0
  80. package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
  81. package/docs/src/content/docs/index.mdx +65 -0
  82. package/docs/src/content/docs/migrating-from-3.mdx +535 -0
  83. package/docs/src/content/docs/packages/core.mdx +186 -0
  84. package/docs/src/content/docs/packages/mcp.mdx +163 -0
  85. package/docs/src/content/docs/packages/react.mdx +207 -0
  86. package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
  87. package/docs/src/content/docs/versioning.mdx +104 -0
  88. package/docs/src/content.config.ts +7 -0
  89. package/docs/src/styles/custom.css +189 -0
  90. package/docs/tsconfig.typedoc.json +26 -0
  91. package/docs/typedoc-entry.ts +17 -0
  92. package/docs/typedoc.json +37 -0
  93. package/e2e/Cargo.lock +646 -0
  94. package/e2e/Cargo.toml +3 -0
  95. package/e2e/README.md +101 -0
  96. package/e2e/frontends/build.mjs +94 -0
  97. package/e2e/frontends/index.html +10 -0
  98. package/e2e/global-setup.ts +65 -0
  99. package/e2e/icp.yaml +38 -0
  100. package/e2e/package.json +40 -0
  101. package/e2e/setup.ts +6 -0
  102. package/e2e/src/actor/Cargo.toml +14 -0
  103. package/e2e/src/actor/hello_actor.did +10 -0
  104. package/e2e/src/actor/src/lib.rs +73 -0
  105. package/e2e/src/calls.test.ts +132 -0
  106. package/e2e/src/declarations/hello_actor.envelope.json +175 -0
  107. package/e2e/src/declarations/hello_actor.ts +15 -0
  108. package/e2e/src/errors.test.ts +107 -0
  109. package/e2e/src/frontend-headers.test.ts +253 -0
  110. package/e2e/src/identity.test.ts +61 -0
  111. package/e2e/src/react.test.tsx +59 -0
  112. package/e2e/src/replica.ts +70 -0
  113. package/e2e/src/vite-env.test.ts +108 -0
  114. package/e2e/test.sh +47 -0
  115. package/e2e/tsconfig.json +20 -0
  116. package/e2e/tsconfig.typecheck.json +9 -0
  117. package/e2e/vitest.config.ts +22 -0
  118. package/eslint.config.mjs +100 -0
  119. package/evals/PREREGISTRATION.md +1346 -0
  120. package/evals/README.md +756 -0
  121. package/evals/conditions/thin/condition.json +8 -0
  122. package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
  123. package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
  124. package/evals/conditions/thin/package.json +25 -0
  125. package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
  126. package/evals/conditions/thin-guide/condition.json +10 -0
  127. package/evals/conditions/thin-guide/docs/llms.txt +292 -0
  128. package/evals/conditions/v3/condition.json +9 -0
  129. package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
  130. package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
  131. package/evals/conditions/v3/docs/llms-full.txt +1053 -0
  132. package/evals/conditions/v3/package.json +26 -0
  133. package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
  134. package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
  135. package/evals/conditions/v4/PORTING.md +384 -0
  136. package/evals/conditions/v4/README.md +106 -0
  137. package/evals/conditions/v4/condition.json +5 -0
  138. package/evals/conditions/v4/docs/llms.txt +323 -0
  139. package/evals/conditions/v4/package.json +27 -0
  140. package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
  141. package/evals/conditions/v4-proto/condition.json +6 -0
  142. package/evals/conditions/v4-proto/docs/llms.txt +291 -0
  143. package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
  144. package/evals/conditions/v4-proto/lib/package.json +27 -0
  145. package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
  146. package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
  147. package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
  148. package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
  149. package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
  150. package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
  151. package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
  152. package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
  153. package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
  154. package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
  155. package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
  156. package/evals/conditions/v4-proto/package.json +26 -0
  157. package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
  158. package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
  159. package/evals/drive.mjs +1221 -0
  160. package/evals/gate.mjs +161 -0
  161. package/evals/harness/aggregate.mjs +534 -0
  162. package/evals/harness/aggregate.test.mjs +344 -0
  163. package/evals/harness/assemble.mjs +216 -0
  164. package/evals/harness/assemble.test.mjs +143 -0
  165. package/evals/harness/auth.mjs +91 -0
  166. package/evals/harness/check-docs.mjs +157 -0
  167. package/evals/harness/declarations/icrc1.did.d.ts +95 -0
  168. package/evals/harness/declarations/icrc1.did.js +140 -0
  169. package/evals/harness/drive-auth.test.mjs +584 -0
  170. package/evals/harness/drive.test.mjs +509 -0
  171. package/evals/harness/fake-auth.ts +67 -0
  172. package/evals/harness/fake-ledger.ts +178 -0
  173. package/evals/harness/gate-plan.mjs +104 -0
  174. package/evals/harness/gate-plan.test.mjs +243 -0
  175. package/evals/harness/icrc1.did +88 -0
  176. package/evals/harness/judge.mjs +63 -0
  177. package/evals/harness/judge.test.mjs +81 -0
  178. package/evals/harness/leak-scan.mjs +1373 -0
  179. package/evals/harness/leak-scan.test.mjs +1300 -0
  180. package/evals/harness/probes/amounts.ts +14 -0
  181. package/evals/harness/probes/node-tool.probe.ts +48 -0
  182. package/evals/harness/probes/probe.mjs +149 -0
  183. package/evals/harness/probes/react-wallet.probe.ts +96 -0
  184. package/evals/harness/public/support/auth.ts +38 -0
  185. package/evals/harness/public/support/fake-replica.d.ts +28 -0
  186. package/evals/harness/public/support/fake-replica.js +434 -0
  187. package/evals/harness/public/support/ledger.idl.d.ts +95 -0
  188. package/evals/harness/public/support/ledger.idl.js +140 -0
  189. package/evals/harness/public/support/world.ts +179 -0
  190. package/evals/harness/runs.mjs +139 -0
  191. package/evals/harness/sandbox.mjs +88 -0
  192. package/evals/harness/sandbox.test.mjs +134 -0
  193. package/evals/harness/ship.mjs +264 -0
  194. package/evals/harness/ship.test.mjs +552 -0
  195. package/evals/harness/world.ts +291 -0
  196. package/evals/package.json +26 -0
  197. package/evals/pnpm-lock.yaml +1537 -0
  198. package/evals/pnpm-workspace.yaml +12 -0
  199. package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
  200. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
  201. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
  202. package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
  203. package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
  204. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
  205. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
  206. package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
  207. package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
  208. package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
  209. package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
  210. package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
  211. package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
  212. package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
  213. package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
  214. package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
  215. package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
  216. package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
  217. package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
  218. package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
  219. package/evals/results/README.md +31 -0
  220. package/evals/runs-pilot.log +150 -0
  221. package/evals/runs-pilot2.log +150 -0
  222. package/evals/score.mjs +256 -0
  223. package/evals/setup.mjs +547 -0
  224. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
  225. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
  226. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
  227. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
  228. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
  229. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
  230. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
  231. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
  232. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
  233. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
  234. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
  235. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
  236. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
  237. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
  238. package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
  239. package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
  240. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  241. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
  242. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
  243. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
  244. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
  245. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
  246. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
  247. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
  248. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
  249. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
  250. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
  251. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
  252. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
  253. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
  254. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
  255. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
  256. package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
  257. package/evals/tasks/node-tool/prompt.md +55 -0
  258. package/evals/tasks/node-tool/prompt.minimal.md +30 -0
  259. package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
  260. package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
  261. package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
  262. package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
  263. package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
  264. package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
  265. package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
  266. package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
  267. package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
  268. package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
  269. package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
  270. package/evals/tasks/node-tool/starter/src/index.ts +7 -0
  271. package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
  272. package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
  273. package/evals/tasks/node-tool/task.json +63 -0
  274. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
  275. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
  276. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
  277. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
  278. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
  279. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
  280. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
  281. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
  282. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
  283. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
  284. package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
  285. package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
  286. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
  287. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
  288. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  289. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
  290. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
  291. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
  292. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
  293. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
  294. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
  295. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
  296. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
  297. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
  298. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
  299. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
  300. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
  301. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
  302. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
  303. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
  304. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
  305. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
  306. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
  307. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
  308. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
  309. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
  310. package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
  311. package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
  312. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
  313. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
  314. package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
  315. package/evals/tasks/react-wallet/prompt.md +88 -0
  316. package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
  317. package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
  318. package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
  319. package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
  320. package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
  321. package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
  322. package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
  323. package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
  324. package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
  325. package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
  326. package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
  327. package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
  328. package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
  329. package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
  330. package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
  331. package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
  332. package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
  333. package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
  334. package/evals/tasks/react-wallet/task.json +84 -0
  335. package/examples/canister-studio/.stackblitzrc +4 -0
  336. package/examples/canister-studio/README.md +183 -0
  337. package/examples/canister-studio/did/ckbtc_minter.did +806 -0
  338. package/examples/canister-studio/did/icp_ledger.did +586 -0
  339. package/examples/canister-studio/did/icrc1.did +88 -0
  340. package/examples/canister-studio/package.json +45 -0
  341. package/examples/canister-studio/scripts/smoke.ts +188 -0
  342. package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
  343. package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
  344. package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
  345. package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
  346. package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
  347. package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
  348. package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
  349. package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
  350. package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
  351. package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
  352. package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
  353. package/examples/canister-studio/src/calls/dedupe.ts +189 -0
  354. package/examples/canister-studio/src/calls/error-info.ts +168 -0
  355. package/examples/canister-studio/src/calls/history.test.ts +284 -0
  356. package/examples/canister-studio/src/calls/history.ts +287 -0
  357. package/examples/canister-studio/src/calls/kinds.ts +68 -0
  358. package/examples/canister-studio/src/calls/prepare.ts +32 -0
  359. package/examples/canister-studio/src/calls/query-options.ts +30 -0
  360. package/examples/canister-studio/src/calls/read.ts +47 -0
  361. package/examples/canister-studio/src/calls/readback.ts +100 -0
  362. package/examples/canister-studio/src/calls/record.ts +123 -0
  363. package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
  364. package/examples/canister-studio/src/calls/share.ts +46 -0
  365. package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
  366. package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
  367. package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
  368. package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
  369. package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
  370. package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
  371. package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
  372. package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
  373. package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
  374. package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
  375. package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
  376. package/examples/canister-studio/src/components/AppShell.tsx +29 -0
  377. package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
  378. package/examples/canister-studio/src/components/Badge.tsx +79 -0
  379. package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
  380. package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
  381. package/examples/canister-studio/src/components/Card.tsx +64 -0
  382. package/examples/canister-studio/src/components/Code.tsx +101 -0
  383. package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
  384. package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
  385. package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
  386. package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
  387. package/examples/canister-studio/src/components/MethodList.tsx +244 -0
  388. package/examples/canister-studio/src/components/Notice.tsx +36 -0
  389. package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
  390. package/examples/canister-studio/src/components/RouteError.tsx +42 -0
  391. package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
  392. package/examples/canister-studio/src/components/Spinner.tsx +33 -0
  393. package/examples/canister-studio/src/components/Tabs.tsx +96 -0
  394. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
  395. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
  396. package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
  397. package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
  398. package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
  399. package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
  400. package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
  401. package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
  402. package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
  403. package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
  404. package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
  405. package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
  406. package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
  407. package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
  408. package/examples/canister-studio/src/components/canister-names.ts +11 -0
  409. package/examples/canister-studio/src/components/download.ts +34 -0
  410. package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
  411. package/examples/canister-studio/src/components/helpers.test.ts +107 -0
  412. package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
  413. package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
  414. package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
  415. package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
  416. package/examples/canister-studio/src/components/home/featured.ts +64 -0
  417. package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
  418. package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
  419. package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
  420. package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
  421. package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
  422. package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
  423. package/examples/canister-studio/src/components/modes.ts +31 -0
  424. package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
  425. package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
  426. package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
  427. package/examples/canister-studio/src/components/playground/sample.ts +43 -0
  428. package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
  429. package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
  430. package/examples/canister-studio/src/components/provenance.ts +38 -0
  431. package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
  432. package/examples/canister-studio/src/components/use-task.ts +56 -0
  433. package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
  434. package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
  435. package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
  436. package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
  437. package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
  438. package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
  439. package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
  440. package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
  441. package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
  442. package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
  443. package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
  444. package/examples/canister-studio/src/forms/context.ts +62 -0
  445. package/examples/canister-studio/src/forms/convert.ts +131 -0
  446. package/examples/canister-studio/src/forms/edge.test.ts +252 -0
  447. package/examples/canister-studio/src/forms/engine.test.ts +358 -0
  448. package/examples/canister-studio/src/forms/engine.ts +1492 -0
  449. package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
  450. package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
  451. package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
  452. package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
  453. package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
  454. package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
  455. package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
  456. package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
  457. package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
  458. package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
  459. package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
  460. package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
  461. package/examples/canister-studio/src/forms/hints.ts +128 -0
  462. package/examples/canister-studio/src/forms/json.ts +119 -0
  463. package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
  464. package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
  465. package/examples/canister-studio/src/interface/browser.test.ts +23 -0
  466. package/examples/canister-studio/src/interface/browser.ts +153 -0
  467. package/examples/canister-studio/src/interface/cache.ts +68 -0
  468. package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
  469. package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
  470. package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
  471. package/examples/canister-studio/src/interface/compile.server.ts +262 -0
  472. package/examples/canister-studio/src/interface/compile.test.ts +108 -0
  473. package/examples/canister-studio/src/interface/compile.ts +305 -0
  474. package/examples/canister-studio/src/interface/docs.test.ts +75 -0
  475. package/examples/canister-studio/src/interface/docs.ts +100 -0
  476. package/examples/canister-studio/src/interface/functions.test.ts +42 -0
  477. package/examples/canister-studio/src/interface/functions.ts +200 -0
  478. package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
  479. package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
  480. package/examples/canister-studio/src/interface/live-loader.ts +364 -0
  481. package/examples/canister-studio/src/interface/load.test.ts +328 -0
  482. package/examples/canister-studio/src/interface/load.ts +249 -0
  483. package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
  484. package/examples/canister-studio/src/interface/read-candid.ts +233 -0
  485. package/examples/canister-studio/src/interface/server-result.ts +42 -0
  486. package/examples/canister-studio/src/interface/typed.ts +68 -0
  487. package/examples/canister-studio/src/interface/types.ts +121 -0
  488. package/examples/canister-studio/src/lib/format.test.ts +107 -0
  489. package/examples/canister-studio/src/lib/format.ts +139 -0
  490. package/examples/canister-studio/src/lib/hex.test.ts +43 -0
  491. package/examples/canister-studio/src/lib/hex.ts +33 -0
  492. package/examples/canister-studio/src/lib/ids.test.ts +70 -0
  493. package/examples/canister-studio/src/lib/ids.ts +93 -0
  494. package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
  495. package/examples/canister-studio/src/lib/reactor.ts +55 -0
  496. package/examples/canister-studio/src/lib/registry.test.ts +74 -0
  497. package/examples/canister-studio/src/lib/registry.ts +238 -0
  498. package/examples/canister-studio/src/lib/schema.ts +148 -0
  499. package/examples/canister-studio/src/routeTree.gen.ts +227 -0
  500. package/examples/canister-studio/src/router.test.ts +290 -0
  501. package/examples/canister-studio/src/router.tsx +127 -0
  502. package/examples/canister-studio/src/routes/__root.tsx +58 -0
  503. package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
  504. package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
  505. package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
  506. package/examples/canister-studio/src/routes/history.tsx +55 -0
  507. package/examples/canister-studio/src/routes/index.tsx +169 -0
  508. package/examples/canister-studio/src/routes/playground.tsx +286 -0
  509. package/examples/canister-studio/src/routes/safety.tsx +57 -0
  510. package/examples/canister-studio/src/routes/wallet.tsx +213 -0
  511. package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
  512. package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
  513. package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
  514. package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
  515. package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
  516. package/examples/canister-studio/src/safety/lab-run.ts +283 -0
  517. package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
  518. package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
  519. package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
  520. package/examples/canister-studio/src/safety/scenarios.ts +924 -0
  521. package/examples/canister-studio/src/styles.css +3453 -0
  522. package/examples/canister-studio/src/styles.test.ts +46 -0
  523. package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
  524. package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
  525. package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
  526. package/examples/canister-studio/src/values/Inspector.tsx +133 -0
  527. package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
  528. package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
  529. package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
  530. package/examples/canister-studio/src/values/candid-text.ts +479 -0
  531. package/examples/canister-studio/src/values/hints.ts +58 -0
  532. package/examples/canister-studio/src/values/json.test.ts +66 -0
  533. package/examples/canister-studio/src/values/json.ts +31 -0
  534. package/examples/canister-studio/src/values/leaves.tsx +309 -0
  535. package/examples/canister-studio/src/values/schema.test.ts +123 -0
  536. package/examples/canister-studio/src/values/schema.ts +78 -0
  537. package/examples/canister-studio/src/values/test-router.tsx +21 -0
  538. package/examples/canister-studio/src/values/tree.tsx +670 -0
  539. package/examples/canister-studio/src/values/use-sha256.ts +29 -0
  540. package/examples/canister-studio/tsconfig.json +20 -0
  541. package/examples/canister-studio/vite.config.ts +50 -0
  542. package/examples/canister-studio/vitest.config.ts +20 -0
  543. package/examples/icrc-ledger/.stackblitzrc +4 -0
  544. package/examples/icrc-ledger/README.md +100 -0
  545. package/examples/icrc-ledger/ckbtc_minter.did +12 -0
  546. package/examples/icrc-ledger/icp_ledger.did +147 -0
  547. package/examples/icrc-ledger/icrc1.did +88 -0
  548. package/examples/icrc-ledger/index.html +13 -0
  549. package/examples/icrc-ledger/package.json +40 -0
  550. package/examples/icrc-ledger/scripts/smoke.ts +151 -0
  551. package/examples/icrc-ledger/src/App.tsx +71 -0
  552. package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
  553. package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
  554. package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
  555. package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
  556. package/examples/icrc-ledger/src/blocks.test.ts +247 -0
  557. package/examples/icrc-ledger/src/blocks.ts +140 -0
  558. package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
  559. package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
  560. package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
  561. package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
  562. package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
  563. package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
  564. package/examples/icrc-ledger/src/format.test.ts +41 -0
  565. package/examples/icrc-ledger/src/format.ts +68 -0
  566. package/examples/icrc-ledger/src/ledger.ts +15 -0
  567. package/examples/icrc-ledger/src/main.tsx +10 -0
  568. package/examples/icrc-ledger/src/minter.ts +16 -0
  569. package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
  570. package/examples/icrc-ledger/src/sandbox.ts +281 -0
  571. package/examples/icrc-ledger/src/styles.css +391 -0
  572. package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
  573. package/examples/icrc-ledger/src/transfer-form.ts +97 -0
  574. package/examples/icrc-ledger/tsconfig.json +19 -0
  575. package/examples/icrc-ledger/vite.config.ts +44 -0
  576. package/examples/icrc-ledger/vitest.config.ts +12 -0
  577. package/examples/next-ssr/.stackblitzrc +4 -0
  578. package/examples/next-ssr/README.md +112 -0
  579. package/examples/next-ssr/icrc1.did +88 -0
  580. package/examples/next-ssr/next.config.ts +10 -0
  581. package/examples/next-ssr/package.json +41 -0
  582. package/examples/next-ssr/scripts/smoke.ts +169 -0
  583. package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
  584. package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
  585. package/examples/next-ssr/src/app/account/page.tsx +82 -0
  586. package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
  587. package/examples/next-ssr/src/app/globals.css +278 -0
  588. package/examples/next-ssr/src/app/layout.tsx +34 -0
  589. package/examples/next-ssr/src/app/page.tsx +46 -0
  590. package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
  591. package/examples/next-ssr/src/app/providers.test.tsx +73 -0
  592. package/examples/next-ssr/src/app/providers.tsx +60 -0
  593. package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
  594. package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
  595. package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
  596. package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
  597. package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
  598. package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
  599. package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
  600. package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
  601. package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
  602. package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
  603. package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
  604. package/examples/next-ssr/src/components/server-data.ts +10 -0
  605. package/examples/next-ssr/src/format.ts +46 -0
  606. package/examples/next-ssr/src/ledgers.ts +38 -0
  607. package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
  608. package/examples/next-ssr/src/server/balance-route.ts +157 -0
  609. package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
  610. package/examples/next-ssr/src/server/error-summary.ts +37 -0
  611. package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
  612. package/examples/next-ssr/src/server/parse-owner.ts +34 -0
  613. package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
  614. package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
  615. package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
  616. package/examples/next-ssr/src/server/read-balances.ts +78 -0
  617. package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
  618. package/examples/next-ssr/src/server/request-client.ts +24 -0
  619. package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
  620. package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
  621. package/examples/next-ssr/src/testing/setup.ts +10 -0
  622. package/examples/next-ssr/tsconfig.json +31 -0
  623. package/examples/next-ssr/vitest.config.ts +59 -0
  624. package/examples/node-agent-tool/.stackblitzrc +4 -0
  625. package/examples/node-agent-tool/README.md +165 -0
  626. package/examples/node-agent-tool/icp.yaml +11 -0
  627. package/examples/node-agent-tool/icrc1.did +88 -0
  628. package/examples/node-agent-tool/package.json +32 -0
  629. package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
  630. package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
  631. package/examples/node-agent-tool/src/cli.test.ts +64 -0
  632. package/examples/node-agent-tool/src/cli.ts +23 -0
  633. package/examples/node-agent-tool/src/command-line.ts +159 -0
  634. package/examples/node-agent-tool/src/commands/balance.ts +73 -0
  635. package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
  636. package/examples/node-agent-tool/src/commands/demo.ts +359 -0
  637. package/examples/node-agent-tool/src/commands/info.ts +69 -0
  638. package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
  639. package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
  640. package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
  641. package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
  642. package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
  643. package/examples/node-agent-tool/src/commands/watch.ts +135 -0
  644. package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
  645. package/examples/node-agent-tool/src/context.ts +20 -0
  646. package/examples/node-agent-tool/src/dispatch.ts +70 -0
  647. package/examples/node-agent-tool/src/failure.test.ts +59 -0
  648. package/examples/node-agent-tool/src/failure.ts +176 -0
  649. package/examples/node-agent-tool/src/identity.test.ts +242 -0
  650. package/examples/node-agent-tool/src/identity.ts +228 -0
  651. package/examples/node-agent-tool/src/input.ts +110 -0
  652. package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
  653. package/examples/node-agent-tool/src/ledgers.ts +62 -0
  654. package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
  655. package/examples/node-agent-tool/src/network.test.ts +128 -0
  656. package/examples/node-agent-tool/src/network.ts +101 -0
  657. package/examples/node-agent-tool/src/output.test.ts +208 -0
  658. package/examples/node-agent-tool/src/output.ts +163 -0
  659. package/examples/node-agent-tool/src/run.ts +73 -0
  660. package/examples/node-agent-tool/src/test-kit.ts +167 -0
  661. package/examples/node-agent-tool/tsconfig.json +18 -0
  662. package/examples/node-agent-tool/vitest.config.ts +8 -0
  663. package/examples/vite-wallet/.stackblitzrc +4 -0
  664. package/examples/vite-wallet/Cargo.lock +646 -0
  665. package/examples/vite-wallet/Cargo.toml +5 -0
  666. package/examples/vite-wallet/README.md +107 -0
  667. package/examples/vite-wallet/backend/Cargo.toml +12 -0
  668. package/examples/vite-wallet/backend/backend.did +37 -0
  669. package/examples/vite-wallet/backend/src/lib.rs +133 -0
  670. package/examples/vite-wallet/icp.yaml +44 -0
  671. package/examples/vite-wallet/index.html +13 -0
  672. package/examples/vite-wallet/ledger.did +54 -0
  673. package/examples/vite-wallet/package.json +46 -0
  674. package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
  675. package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
  676. package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
  677. package/examples/vite-wallet/scripts/faucet.ts +41 -0
  678. package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
  679. package/examples/vite-wallet/src/AddressBook.tsx +147 -0
  680. package/examples/vite-wallet/src/App.tsx +53 -0
  681. package/examples/vite-wallet/src/Balance.test.tsx +57 -0
  682. package/examples/vite-wallet/src/Balance.tsx +58 -0
  683. package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
  684. package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
  685. package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
  686. package/examples/vite-wallet/src/Profile.test.tsx +145 -0
  687. package/examples/vite-wallet/src/Profile.tsx +92 -0
  688. package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
  689. package/examples/vite-wallet/src/SendIcp.tsx +244 -0
  690. package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
  691. package/examples/vite-wallet/src/SendToContact.tsx +102 -0
  692. package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
  693. package/examples/vite-wallet/src/SignIn.tsx +120 -0
  694. package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
  695. package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
  696. package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
  697. package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
  698. package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
  699. package/examples/vite-wallet/src/auth/local-page.ts +20 -0
  700. package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
  701. package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
  702. package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
  703. package/examples/vite-wallet/src/canisters/backend.ts +58 -0
  704. package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
  705. package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
  706. package/examples/vite-wallet/src/client.ts +45 -0
  707. package/examples/vite-wallet/src/main.tsx +18 -0
  708. package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
  709. package/examples/vite-wallet/src/refusal.ts +17 -0
  710. package/examples/vite-wallet/src/styles.css +282 -0
  711. package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
  712. package/examples/vite-wallet/src/token.ts +50 -0
  713. package/examples/vite-wallet/src/transfer.test.ts +178 -0
  714. package/examples/vite-wallet/src/transfer.ts +110 -0
  715. package/examples/vite-wallet/src/use-canisters.ts +49 -0
  716. package/examples/vite-wallet/tsconfig.json +19 -0
  717. package/examples/vite-wallet/vite.config.ts +47 -0
  718. package/examples/vite-wallet/vitest.config.ts +17 -0
  719. package/package.json +9 -4
  720. package/packages/core/.size-limit.js +47 -0
  721. package/packages/core/LICENSE +21 -0
  722. package/packages/core/README.md +84 -0
  723. package/packages/core/llms.txt +610 -0
  724. package/packages/core/package.json +89 -0
  725. package/packages/core/src/call.ts +961 -0
  726. package/packages/core/src/canister.ts +259 -0
  727. package/packages/core/src/client.ts +2061 -0
  728. package/packages/core/src/errors.ts +1079 -0
  729. package/packages/core/src/hydration.ts +239 -0
  730. package/packages/core/src/index.ts +23 -0
  731. package/packages/core/src/keys.ts +108 -0
  732. package/packages/core/src/management.ts +138 -0
  733. package/packages/core/src/network.ts +1020 -0
  734. package/packages/core/src/options.ts +1000 -0
  735. package/packages/core/src/runtime.ts +50 -0
  736. package/packages/core/src/testing/fake-replica.ts +1166 -0
  737. package/packages/core/src/testing/index.ts +16 -0
  738. package/packages/core/src/testing/test-auth.ts +340 -0
  739. package/packages/core/src/testing/test-client.ts +559 -0
  740. package/packages/core/src/types.ts +541 -0
  741. package/packages/core/src/units.ts +299 -0
  742. package/packages/core/src/version.ts +4 -0
  743. package/packages/core/tests/canister-helpers.ts +187 -0
  744. package/packages/core/tests/canister-id.test.ts +158 -0
  745. package/packages/core/tests/canister-unwrap.test.ts +94 -0
  746. package/packages/core/tests/canister.test-d.ts +406 -0
  747. package/packages/core/tests/canister.test.ts +1921 -0
  748. package/packages/core/tests/client-account-change.test.ts +131 -0
  749. package/packages/core/tests/client-agents.test.ts +402 -0
  750. package/packages/core/tests/client-auth-network.test.ts +719 -0
  751. package/packages/core/tests/client-disposed.test.ts +403 -0
  752. package/packages/core/tests/client-helpers.ts +136 -0
  753. package/packages/core/tests/client-hydration.test.ts +166 -0
  754. package/packages/core/tests/client-pinned.test.ts +626 -0
  755. package/packages/core/tests/client-session.test.ts +348 -0
  756. package/packages/core/tests/client.test-d.ts +181 -0
  757. package/packages/core/tests/client.test.ts +1162 -0
  758. package/packages/core/tests/encode.test.ts +171 -0
  759. package/packages/core/tests/env-canister-id.test.ts +666 -0
  760. package/packages/core/tests/env-cookie-copies.test.ts +182 -0
  761. package/packages/core/tests/error-guard.test.ts +222 -0
  762. package/packages/core/tests/errors.test-d.ts +280 -0
  763. package/packages/core/tests/errors.test.ts +1359 -0
  764. package/packages/core/tests/fixtures/README.md +28 -0
  765. package/packages/core/tests/fixtures/accounts.did +21 -0
  766. package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
  767. package/packages/core/tests/fixtures/accounts.ts +26 -0
  768. package/packages/core/tests/fixtures/archive.did +31 -0
  769. package/packages/core/tests/fixtures/archive.envelope.json +259 -0
  770. package/packages/core/tests/fixtures/archive.ts +55 -0
  771. package/packages/core/tests/fixtures/icrc1.did +88 -0
  772. package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
  773. package/packages/core/tests/fixtures/icrc1.ts +78 -0
  774. package/packages/core/tests/fixtures/management.did +724 -0
  775. package/packages/core/tests/fixtures/management.envelope.json +4455 -0
  776. package/packages/core/tests/fixtures/management.ts +486 -0
  777. package/packages/core/tests/fixtures/shapes.did +33 -0
  778. package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
  779. package/packages/core/tests/fixtures/shapes.ts +56 -0
  780. package/packages/core/tests/fixtures/skippable.did +15 -0
  781. package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
  782. package/packages/core/tests/fixtures/skippable.ts +33 -0
  783. package/packages/core/tests/func.test.ts +154 -0
  784. package/packages/core/tests/identity-attributes.test.ts +214 -0
  785. package/packages/core/tests/keys.test.ts +289 -0
  786. package/packages/core/tests/management.test.ts +206 -0
  787. package/packages/core/tests/network-helpers.ts +121 -0
  788. package/packages/core/tests/network.test-d.ts +78 -0
  789. package/packages/core/tests/network.test.ts +1051 -0
  790. package/packages/core/tests/options.test.ts +1594 -0
  791. package/packages/core/tests/redirect-sign-in.test.ts +393 -0
  792. package/packages/core/tests/resend.test-d.ts +81 -0
  793. package/packages/core/tests/resend.test.ts +723 -0
  794. package/packages/core/tests/skip.test-d.ts +125 -0
  795. package/packages/core/tests/testing-entry.test.ts +73 -0
  796. package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
  797. package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
  798. package/packages/core/tests/testing-test-auth.test.ts +483 -0
  799. package/packages/core/tests/testing-test-client.test-d.ts +184 -0
  800. package/packages/core/tests/testing-test-client.test.ts +1272 -0
  801. package/packages/core/tests/time-realm.test.ts +63 -0
  802. package/packages/core/tests/time.test.ts +238 -0
  803. package/packages/core/tests/traps/README.md +39 -0
  804. package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
  805. package/packages/core/tests/traps/classification-table.test.ts +256 -0
  806. package/packages/core/tests/traps/err-arm.test.ts +144 -0
  807. package/packages/core/tests/traps/fetch-spy.ts +39 -0
  808. package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
  809. package/packages/core/tests/traps/ledger.ts +248 -0
  810. package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
  811. package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
  812. package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
  813. package/packages/core/tests/traps/update-as-query.test.ts +244 -0
  814. package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
  815. package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
  816. package/packages/core/tests/traps.test-d.ts +476 -0
  817. package/packages/core/tests/units.test-d.ts +118 -0
  818. package/packages/core/tests/units.test.ts +634 -0
  819. package/packages/core/tsconfig.json +19 -0
  820. package/packages/core/tsconfig.typecheck.json +16 -0
  821. package/packages/core/vitest.config.ts +13 -0
  822. package/packages/mcp/LICENSE +21 -0
  823. package/packages/mcp/README.md +189 -0
  824. package/packages/mcp/package.json +62 -0
  825. package/packages/mcp/src/candid/candid-text.test.ts +455 -0
  826. package/packages/mcp/src/candid/candid-text.ts +501 -0
  827. package/packages/mcp/src/candid/contract.test.ts +398 -0
  828. package/packages/mcp/src/candid/formats.ts +155 -0
  829. package/packages/mcp/src/candid/hex.ts +29 -0
  830. package/packages/mcp/src/candid/json-schema.test.ts +758 -0
  831. package/packages/mcp/src/candid/json-schema.ts +454 -0
  832. package/packages/mcp/src/candid/json.test.ts +1207 -0
  833. package/packages/mcp/src/candid/json.ts +1053 -0
  834. package/packages/mcp/src/candid/schema-walk.ts +189 -0
  835. package/packages/mcp/src/candid/type-text.test.ts +179 -0
  836. package/packages/mcp/src/candid/type-text.ts +238 -0
  837. package/packages/mcp/src/cli.ts +134 -0
  838. package/packages/mcp/src/identity.ts +188 -0
  839. package/packages/mcp/src/interface/cache.test.ts +48 -0
  840. package/packages/mcp/src/interface/cache.ts +68 -0
  841. package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
  842. package/packages/mcp/src/interface/cli-cache.ts +122 -0
  843. package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
  844. package/packages/mcp/src/interface/compile-worker.ts +290 -0
  845. package/packages/mcp/src/interface/compile.test.ts +155 -0
  846. package/packages/mcp/src/interface/compile.ts +281 -0
  847. package/packages/mcp/src/interface/docs.test.ts +105 -0
  848. package/packages/mcp/src/interface/docs.ts +86 -0
  849. package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
  850. package/packages/mcp/src/interface/hex.ts +8 -0
  851. package/packages/mcp/src/interface/ids.test.ts +71 -0
  852. package/packages/mcp/src/interface/ids.ts +87 -0
  853. package/packages/mcp/src/interface/loader.test.ts +804 -0
  854. package/packages/mcp/src/interface/loader.ts +519 -0
  855. package/packages/mcp/src/interface/read-candid.test.ts +300 -0
  856. package/packages/mcp/src/interface/read-candid.ts +259 -0
  857. package/packages/mcp/src/interface/types.ts +113 -0
  858. package/packages/mcp/src/network.ts +122 -0
  859. package/packages/mcp/src/options.ts +149 -0
  860. package/packages/mcp/src/server/approval.ts +159 -0
  861. package/packages/mcp/src/server/naming.ts +43 -0
  862. package/packages/mcp/src/server/results.ts +102 -0
  863. package/packages/mcp/src/server/server.ts +797 -0
  864. package/packages/mcp/src/server/tools.ts +116 -0
  865. package/packages/mcp/src/usage.ts +40 -0
  866. package/packages/mcp/tests/approval.test.ts +60 -0
  867. package/packages/mcp/tests/identity.test.ts +234 -0
  868. package/packages/mcp/tests/naming.test.ts +62 -0
  869. package/packages/mcp/tests/options.test.ts +140 -0
  870. package/packages/mcp/tests/server.test.ts +997 -0
  871. package/packages/mcp/tsconfig.json +19 -0
  872. package/packages/mcp/tsconfig.typecheck.json +10 -0
  873. package/packages/mcp/vitest.config.ts +10 -0
  874. package/packages/react/LICENSE +21 -0
  875. package/packages/react/README.md +328 -0
  876. package/packages/react/llms.txt +95 -0
  877. package/packages/react/package.json +95 -0
  878. package/packages/react/src/index.tsx +678 -0
  879. package/packages/react/tests/abandoned-render.test.tsx +816 -0
  880. package/packages/react/tests/auth.test.tsx +315 -0
  881. package/packages/react/tests/caller-keys.test.tsx +2334 -0
  882. package/packages/react/tests/client-time.test.ts +40 -0
  883. package/packages/react/tests/entry.test.ts +52 -0
  884. package/packages/react/tests/helpers.tsx +291 -0
  885. package/packages/react/tests/hydration.test.tsx +262 -0
  886. package/packages/react/tests/ownership.test.tsx +260 -0
  887. package/packages/react/tests/provider.server.test.tsx +169 -0
  888. package/packages/react/tests/query-client.test.tsx +107 -0
  889. package/packages/react/tests/strict-mode.test.tsx +288 -0
  890. package/packages/react/tests/suspense.test-d.tsx +357 -0
  891. package/packages/react/tests/two-query-cores.test.tsx +135 -0
  892. package/packages/react/tests/types.test-d.tsx +63 -0
  893. package/packages/react/tsconfig.json +23 -0
  894. package/packages/react/tsconfig.test.json +8 -0
  895. package/packages/react/tsconfig.typecheck.json +16 -0
  896. package/packages/react/vitest.config.ts +39 -0
  897. package/packages/vite-plugin/LICENSE +21 -0
  898. package/packages/vite-plugin/README.md +532 -0
  899. package/packages/vite-plugin/package.json +76 -0
  900. package/packages/vite-plugin/src/describe-value.test.ts +43 -0
  901. package/packages/vite-plugin/src/describe-value.ts +167 -0
  902. package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
  903. package/packages/vite-plugin/src/dev-environment.ts +510 -0
  904. package/packages/vite-plugin/src/env.ts +431 -0
  905. package/packages/vite-plugin/src/fetch.ts +348 -0
  906. package/packages/vite-plugin/src/file-identity.ts +106 -0
  907. package/packages/vite-plugin/src/generate.ts +691 -0
  908. package/packages/vite-plugin/src/index.test.ts +1243 -0
  909. package/packages/vite-plugin/src/index.ts +2565 -0
  910. package/packages/vite-plugin/src/interface.ts +339 -0
  911. package/packages/vite-plugin/src/local-host.ts +34 -0
  912. package/packages/vite-plugin/src/options.ts +641 -0
  913. package/packages/vite-plugin/src/principal-text.test.ts +87 -0
  914. package/packages/vite-plugin/src/principal-text.ts +98 -0
  915. package/packages/vite-plugin/tests/close.test.ts +141 -0
  916. package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
  917. package/packages/vite-plugin/tests/fake-ic.ts +214 -0
  918. package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
  919. package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
  920. package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
  921. package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
  922. package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
  923. package/packages/vite-plugin/tests/generation.test.ts +1036 -0
  924. package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
  925. package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
  926. package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
  927. package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
  928. package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
  929. package/packages/vite-plugin/tests/package.test.ts +62 -0
  930. package/packages/vite-plugin/tests/project.test.ts +2993 -0
  931. package/packages/vite-plugin/tests/support.ts +339 -0
  932. package/packages/vite-plugin/tests/timeout.test.ts +107 -0
  933. package/packages/vite-plugin/tsconfig.json +23 -0
  934. package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
  935. package/packages/vite-plugin/vitest.config.ts +9 -0
  936. package/pnpm-workspace.yaml +23 -0
  937. package/scripts/ai-context-files.js +72 -0
  938. package/scripts/assemble-docs-site.mjs +485 -0
  939. package/scripts/assemble-docs-site.test.mjs +568 -0
  940. package/scripts/build-examples.js +58 -0
  941. package/scripts/check-ai-context.js +963 -0
  942. package/scripts/check-ai-context.test.mjs +811 -0
  943. package/scripts/check-exports.mjs +767 -0
  944. package/scripts/check-exports.test.mjs +1136 -0
  945. package/scripts/check-snippets/ambient.d.ts +428 -0
  946. package/scripts/check-snippets/app/generated/backend.did +7 -0
  947. package/scripts/check-snippets/app/generated/backend.ts +15 -0
  948. package/scripts/check-snippets/app/generated/icrc1.did +88 -0
  949. package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
  950. package/scripts/check-snippets/app/globals.ts +26 -0
  951. package/scripts/check-snippets/docs/guides/App.tsx +21 -0
  952. package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
  953. package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
  954. package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
  955. package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
  956. package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
  957. package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
  958. package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
  959. package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
  960. package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
  961. package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
  962. package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
  963. package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
  964. package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
  965. package/scripts/check-snippets.mjs +993 -0
  966. package/scripts/check-snippets.test.mjs +338 -0
  967. package/scripts/check-stack.mjs +501 -0
  968. package/scripts/check-stack.test.mjs +800 -0
  969. package/scripts/clean-node-modules.js +67 -0
  970. package/scripts/example-projects.js +58 -0
  971. package/scripts/export-budget.mjs +145 -0
  972. package/scripts/faults.json +6749 -0
  973. package/scripts/lib/fault-workspace.mjs +210 -0
  974. package/scripts/lib/io.mjs +29 -0
  975. package/scripts/lib/io.test.mjs +38 -0
  976. package/scripts/lib/markdown-fences.mjs +56 -0
  977. package/scripts/lib/markdown-fences.test.mjs +84 -0
  978. package/scripts/lib/package-exports.mjs +54 -0
  979. package/scripts/lib/package-exports.test.mjs +66 -0
  980. package/scripts/lib/release-pr-steps.mjs +47 -0
  981. package/scripts/release-tag.mjs +95 -0
  982. package/scripts/release-tag.test.mjs +278 -0
  983. package/scripts/release-workflow.test.mjs +98 -0
  984. package/scripts/release.js +167 -0
  985. package/scripts/release.test.mjs +347 -0
  986. package/scripts/removed-v3-names.js +125 -0
  987. package/scripts/size-app/.size-limit.js +90 -0
  988. package/scripts/size-app/core.js +3 -0
  989. package/scripts/size-app/package.json +24 -0
  990. package/scripts/size-app/react.js +4 -0
  991. package/scripts/size-app.test.mjs +73 -0
  992. package/scripts/sync-ai-context-versions.js +102 -0
  993. package/scripts/sync-example-versions.js +151 -0
  994. package/scripts/test-examples-published.mjs +1585 -0
  995. package/scripts/test-examples-published.test.mjs +1586 -0
  996. package/scripts/traps/any-method-vars-not-a-list.json +5 -0
  997. package/scripts/traps/canister-method-unknown.json +5 -0
  998. package/scripts/traps/client-says-who-calls-once.json +5 -0
  999. package/scripts/traps/client-says-who-calls.json +5 -0
  1000. package/scripts/traps/err-only-on-canister-err.json +14 -0
  1001. package/scripts/traps/format-units-refuses-missing-value.json +5 -0
  1002. package/scripts/traps/format-units-refuses-number.json +5 -0
  1003. package/scripts/traps/format-units-unknown-option.json +5 -0
  1004. package/scripts/traps/guard-err-is-the-methods.json +5 -0
  1005. package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
  1006. package/scripts/traps/internals-not-exported.json +9 -0
  1007. package/scripts/traps/network-refuses-unknown-name.json +5 -0
  1008. package/scripts/traps/never-err-has-no-type.json +5 -0
  1009. package/scripts/traps/options-method-unknown.json +5 -0
  1010. package/scripts/traps/options-vars-wrong-type.json +5 -0
  1011. package/scripts/traps/parse-units-refuses-number.json +5 -0
  1012. package/scripts/traps/parse-units-unknown-option.json +5 -0
  1013. package/scripts/traps/principal-is-not-plain-text.json +14 -0
  1014. package/scripts/traps/query-options-refuses-oneway.json +5 -0
  1015. package/scripts/traps/query-options-refuses-update.json +5 -0
  1016. package/scripts/traps/refuse-next-unknown-option.json +5 -0
  1017. package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
  1018. package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
  1019. package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
  1020. package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
  1021. package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
  1022. package/scripts/traps/test-handler-method-unknown.json +5 -0
  1023. package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
  1024. package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
  1025. package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
  1026. package/scripts/typecheck-examples.js +122 -0
  1027. package/scripts/verify-faults.mjs +375 -0
  1028. package/scripts/verify-faults.test.mjs +402 -0
  1029. package/scripts/verify-package-artifacts.mjs +590 -0
  1030. package/scripts/verify-peer-floors.js +399 -0
  1031. package/scripts/verify-peer-floors.test.mjs +422 -0
  1032. package/scripts/verify-test-fails-rules.mjs +104 -0
  1033. package/scripts/verify-test-fails-rules.test.mjs +215 -0
  1034. package/scripts/verify-test-fails.js +371 -0
  1035. package/scripts/verify-traps.mjs +505 -0
  1036. package/scripts/verify-traps.test.mjs +521 -0
  1037. package/scripts/verify-typescript-floor.js +239 -0
  1038. package/skill-packages/ic-reactor/SKILL.md +103 -0
  1039. package/stack.json +8 -0
  1040. package/tsconfig.base.json +23 -0
  1041. package/tsconfig.json +8 -0
@@ -0,0 +1,1594 @@
1
+ /**
2
+ * `client.queryOptions` and `client.mutationOptions`, driven by real TanStack
3
+ * Query observers on the client's own `QueryClient`, against a fake replica.
4
+ */
5
+ import { afterEach, describe, expect, it, vi } from "vitest"
6
+ import { principal, type Principal } from "@meantjs/schema"
7
+ import { HttpErrorCode, ProtocolError } from "@icp-sdk/core/agent"
8
+ import { Ed25519KeyIdentity } from "@icp-sdk/core/identity"
9
+ import {
10
+ MutationObserver,
11
+ QueryClient,
12
+ QueryObserver,
13
+ dehydrate,
14
+ focusManager,
15
+ hydrate,
16
+ onlineManager,
17
+ skipToken,
18
+ type MutationObserverOptions,
19
+ type QueryKey,
20
+ } from "@tanstack/query-core"
21
+ import { classifyError, type ReactorError } from "../src/errors.js"
22
+ import { createClient, type Client } from "../src/index.js"
23
+ import type { CanisterMutationOptions } from "../src/types.js"
24
+ import { createTestAuth } from "../src/testing/test-auth.js"
25
+ import {
26
+ ANONYMOUS,
27
+ ARCHIVE,
28
+ FEE,
29
+ LEDGER,
30
+ SHAPES,
31
+ canisterRequests,
32
+ clientAs,
33
+ clientWithAuth,
34
+ deferred,
35
+ ledgerCanister,
36
+ replicaWith,
37
+ requestsFor,
38
+ serve,
39
+ sleep,
40
+ type HandWritten,
41
+ } from "./canister-helpers.js"
42
+ import { withIdentity } from "./client-helpers.js"
43
+ import * as icrc1 from "./fixtures/icrc1.js"
44
+ import * as shapes from "./fixtures/shapes.js"
45
+ import * as skippable from "./fixtures/skippable.js"
46
+ import { icEnvCookie, stubPage } from "./network-helpers.js"
47
+
48
+ afterEach(() => {
49
+ vi.unstubAllGlobals()
50
+ focusManager.setFocused(undefined)
51
+ onlineManager.setOnline(true)
52
+ })
53
+
54
+ const alice = Ed25519KeyIdentity.generate()
55
+ const ALICE = principal(alice.getPrincipal().toText())
56
+ const BOB = principal(Ed25519KeyIdentity.generate().getPrincipal().toText())
57
+
58
+ /** The canister a local redeploy moves a `{ name }` to, in the tests that redeploy. */
59
+ const REDEPLOYED = ARCHIVE
60
+
61
+ /**
62
+ * Another canister of the shapes interface, reached by its id, in the test
63
+ * that writes to one canister and lists a `{ name }` in `invalidates`.
64
+ */
65
+ const BY_ID = "r7inp-6aaaa-aaaaa-aaabq-cai"
66
+
67
+ /** A shapes canister whose `who` answers with the caller, held back for a caller while a gate is set. */
68
+ function whoCanister(holds: Map<string, Promise<void>>) {
69
+ return serve<shapes.Actor>(shapes.actor, {
70
+ who: async (_args, { caller }) => {
71
+ await holds.get(caller)
72
+ return principal(caller)
73
+ },
74
+ one: ([n]) => n + 1n,
75
+ address: ([seed], { caller }) => `${seed}:${caller}`,
76
+ bump: ([n]) => ({ tag: "ok", value: n + 1n }),
77
+ note: () => undefined,
78
+ })
79
+ }
80
+
81
+ describe("queryOptions", () => {
82
+ it("reads through the client's QueryClient, as the caller in the key", async () => {
83
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
84
+ const client = clientAs(replica, alice)
85
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
86
+ const options = client.queryOptions(canister, "who", undefined)
87
+ await expect(client.queryClient.fetchQuery(options)).resolves.toBe(ALICE)
88
+ expect(client.queryClient.getQueryData(options.queryKey)).toBe(ALICE)
89
+ expect(requestsFor(replica, "who")).toMatchObject([
90
+ { endpoint: "query", caller: ALICE },
91
+ ])
92
+ })
93
+
94
+ it("never shows one principal's data under another's key, across sign-in, a switch with a read in flight, and sign-out", async () => {
95
+ const holds = new Map<string, Promise<void>>()
96
+ const replica = replicaWith({ [SHAPES]: whoCanister(holds) })
97
+ const auth = createTestAuth({ seed: 1, signedIn: false })
98
+ const client = clientWithAuth(replica, auth)
99
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
100
+ // The options a component would build on each render, without retries
101
+ // so that a refused read settles at once.
102
+ const options = () => ({
103
+ ...client.queryOptions(canister, "who", undefined),
104
+ retry: false,
105
+ })
106
+ const observer = new QueryObserver(client.queryClient, options())
107
+ const shown: { caller: unknown; data: unknown }[] = []
108
+ const unsubscribe = observer.subscribe((result) => {
109
+ shown.push({ caller: observer.options.queryKey[2], data: result.data })
110
+ })
111
+ // A component re-renders when the caller changes, and builds its options
112
+ // again: the read moves to the new caller's key.
113
+ const stop = client.subscribe(() => observer.setOptions(options()))
114
+ const shows = (who: string) =>
115
+ vi.waitFor(() => expect(observer.getCurrentResult().data).toBe(who))
116
+
117
+ await shows(ANONYMOUS)
118
+ await client.signIn()
119
+ const a = client.caller()
120
+ await shows(a)
121
+ const keyOfA: QueryKey = observer.options.queryKey
122
+ const optionsOfA = observer.options
123
+
124
+ // A's next read is held in flight, past the switch to B.
125
+ const held = deferred()
126
+ holds.set(a, held.promise)
127
+ void observer.refetch()
128
+ await vi.waitFor(() =>
129
+ expect(
130
+ requestsFor(replica, "who").filter((r) => r.caller === a)
131
+ ).toHaveLength(2)
132
+ )
133
+ auth.switchTo(2)
134
+ const b = client.caller()
135
+ await shows(b)
136
+ held.resolve()
137
+ holds.delete(a)
138
+ await sleep(100)
139
+ expect(observer.getCurrentResult().data).toBe(b)
140
+
141
+ // A's reads left the cache when B became current, and A's slow answer
142
+ // did not bring them back.
143
+ expect(client.queryClient.getQueryData(keyOfA)).toBeUndefined()
144
+
145
+ // A read of A's key that starts now runs A's query function, made for A:
146
+ // it must not be sent as B, whose answer would land under A's key.
147
+ const sent = requestsFor(replica, "who").length
148
+ await client.queryClient
149
+ .fetchQuery({ ...optionsOfA, staleTime: 0 })
150
+ .catch((error: unknown) => error)
151
+ expect(requestsFor(replica, "who")).toHaveLength(sent)
152
+ expect(client.queryClient.getQueryData(keyOfA)).toBeUndefined()
153
+
154
+ // Back to A: A's key shows A's data and nobody else's.
155
+ auth.switchTo(1)
156
+ await shows(a)
157
+ await auth.signOut()
158
+ await shows(ANONYMOUS)
159
+ await sleep(100)
160
+ stop()
161
+ unsubscribe()
162
+
163
+ const callers = new Set(shown.map(({ caller }) => caller))
164
+ expect(callers).toEqual(new Set([ANONYMOUS, a, b]))
165
+ for (const { caller, data } of shown) {
166
+ if (data !== undefined) expect(data).toBe(caller)
167
+ }
168
+ // Every request went out as the principal of the key it was made for.
169
+ expect(
170
+ requestsFor(replica, "who").every((r) =>
171
+ [ANONYMOUS, a, b].includes(r.caller as string)
172
+ )
173
+ ).toBe(true)
174
+ })
175
+
176
+ it("refuses to run once the caller its key holds is no longer current, and sends nothing", async () => {
177
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
178
+ const auth = createTestAuth({ seed: 1 })
179
+ const client = clientWithAuth(replica, auth)
180
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
181
+ const options = client.queryOptions(canister, "who", undefined)
182
+ auth.switchTo(2)
183
+ await expect(
184
+ client.queryClient.fetchQuery({ ...options, retry: false })
185
+ ).rejects.toMatchObject({
186
+ kind: "cancelled",
187
+ code: "caller_changed",
188
+ mayHaveExecuted: false,
189
+ })
190
+ expect(canisterRequests(replica)).toEqual([])
191
+ })
192
+
193
+ it("does not re-send itself: TanStack's retry decides", async () => {
194
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
195
+ const client = clientAs(replica, alice)
196
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
197
+ replica.refuseNext(503)
198
+ await expect(
199
+ client.queryClient.fetchQuery({
200
+ ...client.queryOptions(canister, "one", 1n),
201
+ retry: false,
202
+ })
203
+ ).rejects.toMatchObject({ kind: "not_delivered", httpStatus: 503 })
204
+ expect(requestsFor(replica, "one")).toHaveLength(1)
205
+ })
206
+
207
+ it("retries only a failure that proves the read was not delivered, and never on a server", () => {
208
+ const replica = replicaWith({})
209
+ const client = clientAs(replica, alice)
210
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
211
+ const { retry } = client.queryOptions(canister, "one", 1n)
212
+ const refused = classifyError(
213
+ ProtocolError.fromCode(new HttpErrorCode(503, "busy", [])),
214
+ { method: "one", canisterId: SHAPES, mode: "query" }
215
+ )
216
+ const rejected = classifyError(
217
+ { kind: "Reject", code: { rejectCode: 4 } },
218
+ { method: "one", canisterId: SHAPES, mode: "query" }
219
+ )
220
+ // Node is a server: nothing is retried.
221
+ expect(retry(0, refused)).toBe(false)
222
+ vi.stubGlobal("window", {})
223
+ expect(retry(0, refused)).toBe(true)
224
+ expect(retry(3, refused)).toBe(false)
225
+ expect(retry(0, rejected)).toBe(false)
226
+ })
227
+
228
+ it("builds a tagged key for arguments that do not encode, and rejects invalid_args when run, sending nothing", async () => {
229
+ const replica = replicaWith({ [LEDGER]: ledgerCanister(new Map()) })
230
+ const client = clientAs(replica, alice)
231
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
232
+ // Half-typed input, as a form holds it mid-edit.
233
+ const typing = { owner: "rrkah-" as Principal, subaccount: null }
234
+ let options: ReturnType<
235
+ typeof client.queryOptions<icrc1.Actor, "icrc1_balance_of">
236
+ >
237
+ expect(() => {
238
+ options = client.queryOptions(ledger, "icrc1_balance_of", typing)
239
+ }).not.toThrow()
240
+ expect(options!.queryKey.slice(4, 6)).toEqual([
241
+ "icrc1_balance_of",
242
+ "$invalid",
243
+ ])
244
+ await expect(
245
+ client.queryClient.fetchQuery({ ...options!, retry: false })
246
+ ).rejects.toMatchObject({
247
+ kind: "invalid_args",
248
+ issues: [{ code: "invalid_type", path: "$args[0].owner" }],
249
+ mayHaveExecuted: false,
250
+ })
251
+ expect(canisterRequests(replica)).toEqual([])
252
+ })
253
+
254
+ it("rejects canister_id_unresolved for an unresolved { name }, sending nothing", async () => {
255
+ const replica = replicaWith({})
256
+ const client = clientAs(replica, alice)
257
+ const byName = client.canister<shapes.Actor>(shapes.actor, {
258
+ name: "backend",
259
+ })
260
+ await expect(
261
+ client.queryClient.fetchQuery({
262
+ ...client.queryOptions(byName, "one", 1n),
263
+ retry: false,
264
+ })
265
+ ).rejects.toMatchObject({
266
+ kind: "invalid_args",
267
+ code: "canister_id_unresolved",
268
+ })
269
+ expect(replica.requests).toEqual([])
270
+ })
271
+
272
+ it("skips a read with skipToken", () => {
273
+ const { client, canister } = (() => {
274
+ const c = clientAs(replicaWith({}), alice)
275
+ return {
276
+ client: c,
277
+ canister: c.canister<shapes.Actor>(shapes.actor, { id: SHAPES }),
278
+ }
279
+ })()
280
+ const options = client.queryOptions(canister, "one", skipToken)
281
+ expect(options.queryFn).toBe(skipToken)
282
+ expect(options.queryKey[options.queryKey.length - 1]).toBe("$skip")
283
+ })
284
+
285
+ it("gives a query function for every value in place of the variables, and skipToken only for skipToken", async () => {
286
+ // The types drop SkipToken from the options' queryFn for variables that
287
+ // cannot be skipToken, so the builder must run each such read: an empty
288
+ // record, a record of opts alone, an opt and null, each null where it can
289
+ // be, and no arguments.
290
+ const replica = replicaWith({
291
+ [SHAPES]: serve<skippable.Actor>(skippable.actor, {
292
+ empty_record: () => 1n,
293
+ only_opts: ([{ limit }]) => limit ?? 2n,
294
+ maybe_nat: ([n]) => n ?? 3n,
295
+ nothing_at_all: () => 4n,
296
+ no_args: () => 5n,
297
+ anything: () => 6n,
298
+ }),
299
+ })
300
+ const client = clientAs(replica, alice)
301
+ const canister = client.canister<skippable.Actor>(skippable.actor, {
302
+ id: SHAPES,
303
+ })
304
+ const reads = [
305
+ [client.queryOptions(canister, "empty_record", {}), 1n],
306
+ [
307
+ client.queryOptions(canister, "only_opts", {
308
+ limit: null,
309
+ prefix: null,
310
+ }),
311
+ 2n,
312
+ ],
313
+ [client.queryOptions(canister, "maybe_nat", null), 3n],
314
+ [client.queryOptions(canister, "nothing_at_all", null), 4n],
315
+ [client.queryOptions(canister, "no_args"), 5n],
316
+ [client.queryOptions(canister, "no_args", undefined), 5n],
317
+ [client.queryOptions(canister, "anything", null), 6n],
318
+ ] as const
319
+ for (const [options, expected] of reads) {
320
+ expect(typeof options.queryFn).toBe("function")
321
+ await expect(client.queryClient.fetchQuery(options)).resolves.toBe(
322
+ expected
323
+ )
324
+ }
325
+ for (const method of [
326
+ "empty_record",
327
+ "only_opts",
328
+ "maybe_nat",
329
+ "nothing_at_all",
330
+ "no_args",
331
+ "anything",
332
+ ] as const) {
333
+ const options = client.queryOptions(canister, method, skipToken)
334
+ expect(options.queryFn).toBe(skipToken)
335
+ }
336
+ })
337
+
338
+ it("refuses another copy's skipToken, which it cannot tell from an argument, and still skips its own and passes a symbol to reserved", async () => {
339
+ const replica = replicaWith({
340
+ [SHAPES]: serve<skippable.Actor>(skippable.actor, {
341
+ anything: () => 6n,
342
+ }),
343
+ })
344
+ const client = clientAs(replica, alice)
345
+ const canister = client.canister<skippable.Actor>(skippable.actor, {
346
+ id: SHAPES,
347
+ })
348
+ // What a second copy of @tanstack/query-core exports: its own Symbol().
349
+ const foreign = Symbol() as never
350
+ const twoCopies =
351
+ /a symbol, not core's skipToken: two copies of @tanstack\/query-core are installed.*`npm ls @tanstack\/query-core` must show one copy/
352
+ expect(() => client.queryOptions(canister, "maybe_nat", foreign)).toThrow(
353
+ twoCopies
354
+ )
355
+ expect(() =>
356
+ client.queryOptions(canister, "empty_record", foreign)
357
+ ).toThrow(TypeError)
358
+ expect(() => client.queryKey(canister, "maybe_nat", foreign)).toThrow(
359
+ twoCopies
360
+ )
361
+ expect(() => client.queryKey(canister, "no_args", foreign)).toThrow(
362
+ twoCopies
363
+ )
364
+ // Its own skipToken still skips.
365
+ expect(client.queryOptions(canister, "maybe_nat", skipToken).queryFn).toBe(
366
+ skipToken
367
+ )
368
+ const skipped = client.queryKey(canister, "maybe_nat", skipToken as never)
369
+ expect(skipped[skipped.length - 1]).toBe("$skip")
370
+ // reserved takes every value, a symbol included: it is encoded and read.
371
+ const options = client.queryOptions(canister, "anything", foreign)
372
+ expect(typeof options.queryFn).toBe("function")
373
+ await expect(client.queryClient.fetchQuery(options)).resolves.toBe(6n)
374
+ expect(() => client.queryKey(canister, "anything", foreign)).not.toThrow()
375
+ })
376
+
377
+ it("throws a TypeError for an update or oneway method, a certified composite query, or an unknown fourth argument", () => {
378
+ const client = clientAs(replicaWith({}), alice)
379
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
380
+ const certified = client.canister<shapes.Actor>(shapes.actor, {
381
+ id: SHAPES,
382
+ certified: true,
383
+ })
384
+ // The generated Actor's update and oneway methods do not compile here
385
+ // (tests/traps.test-d.ts); typed by hand, without modes, they do, and the
386
+ // check at run time refuses them.
387
+ const handWritten = client.canister<HandWritten<shapes.Actor>>(
388
+ shapes.actor,
389
+ { id: SHAPES }
390
+ )
391
+ expect(() => client.queryOptions(handWritten, "bump", 1n)).toThrow(
392
+ /bump is an update method/
393
+ )
394
+ expect(() => client.queryOptions(handWritten, "note", "x")).toThrow(
395
+ TypeError
396
+ )
397
+ expect(() =>
398
+ client.queryOptions(handWritten, "fire", "x", { update: "idempotent" })
399
+ ).toThrow(/oneway/)
400
+ expect(() => client.queryOptions(certified, "composite", 1n)).toThrow(
401
+ /composite query/
402
+ )
403
+ expect(() =>
404
+ client.queryOptions(canister, "one", 1n, {
405
+ update: "always",
406
+ } as unknown as { update: "idempotent" })
407
+ ).toThrow(TypeError)
408
+ expect(() => client.queryOptions(canister, "composite", 1n)).not.toThrow()
409
+ })
410
+
411
+ it("throws a TypeError for a method without results, which leaves a read nothing to cache, and its direct call still resolves", async () => {
412
+ const replica = replicaWith({
413
+ [SHAPES]: serve<shapes.Actor>(shapes.actor, {
414
+ nothing: () => undefined,
415
+ }),
416
+ })
417
+ const client = clientAs(replica, alice)
418
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
419
+ // Its call resolves undefined, which TanStack Query reports as a failed
420
+ // read with nothing cached: refused when the options are built instead.
421
+ expect(() => client.queryOptions(canister, "nothing")).toThrow(TypeError)
422
+ expect(() => client.queryOptions(canister, "nothing", skipToken)).toThrow(
423
+ /nothing has no results, and a method without results has nothing to cache\. Call it directly\./
424
+ )
425
+ // An update without results, opted in as a read, has nothing to cache
426
+ // either.
427
+ expect(() =>
428
+ client.queryOptions(canister, "note", "x", { update: "idempotent" })
429
+ ).toThrow(
430
+ /note has no results.*Call it directly, or through mutationOptions/
431
+ )
432
+ expect(canisterRequests(replica)).toEqual([])
433
+ await expect(canister.nothing()).resolves.toBeUndefined()
434
+ expect(requestsFor(replica, "nothing")).toMatchObject([
435
+ { endpoint: "query" },
436
+ ])
437
+ })
438
+ })
439
+
440
+ describe("an update read as a query, with { update: 'idempotent' }", () => {
441
+ /** A signed-in client in a page, mounted so focus and network events reach its queries. */
442
+ function mounted() {
443
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
444
+ const auth = createTestAuth({ seed: 1 })
445
+ const client = clientWithAuth(replica, auth)
446
+ client.queryClient.mount()
447
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
448
+ return { replica, auth, client, canister }
449
+ }
450
+
451
+ /** Plays out what makes TanStack refetch: losing and regaining focus and the network, and a remount. */
452
+ async function refocusReconnectRemount(
453
+ remount: () => () => void
454
+ ): Promise<void> {
455
+ focusManager.setFocused(false)
456
+ focusManager.setFocused(true)
457
+ await sleep(100)
458
+ onlineManager.setOnline(false)
459
+ onlineManager.setOnline(true)
460
+ await sleep(100)
461
+ const unsubscribe = remount()
462
+ await sleep(100)
463
+ unsubscribe()
464
+ }
465
+
466
+ it("is fetched once and never refetched on focus, reconnect or remount", async () => {
467
+ const { replica, client, canister } = mounted()
468
+ const build = () =>
469
+ client.queryOptions(canister, "address", "x", { update: "idempotent" })
470
+ expect(build()).toMatchObject({
471
+ staleTime: Infinity,
472
+ refetchOnMount: false,
473
+ refetchOnWindowFocus: false,
474
+ refetchOnReconnect: false,
475
+ })
476
+ const observer = new QueryObserver(client.queryClient, build())
477
+ const unsubscribe = observer.subscribe(() => {})
478
+ await vi.waitFor(() =>
479
+ expect(observer.getCurrentResult().data).toBe(`x:${client.caller()}`)
480
+ )
481
+ await refocusReconnectRemount(() =>
482
+ new QueryObserver(client.queryClient, build()).subscribe(() => {})
483
+ )
484
+ unsubscribe()
485
+ client.queryClient.unmount()
486
+ expect(requestsFor(replica, "address")).toMatchObject([
487
+ { endpoint: "call" },
488
+ ])
489
+ })
490
+
491
+ it("is refetched on focus, reconnect and remount when it is a plain read (the harness can see a refetch)", async () => {
492
+ const { replica, client, canister } = mounted()
493
+ const build = () => client.queryOptions(canister, "one", 1n)
494
+ const observer = new QueryObserver(client.queryClient, build())
495
+ const unsubscribe = observer.subscribe(() => {})
496
+ await vi.waitFor(() => expect(observer.getCurrentResult().data).toBe(2n))
497
+ await refocusReconnectRemount(() =>
498
+ new QueryObserver(client.queryClient, build()).subscribe(() => {})
499
+ )
500
+ unsubscribe()
501
+ client.queryClient.unmount()
502
+ expect(requestsFor(replica, "one").length).toBeGreaterThanOrEqual(3)
503
+ })
504
+
505
+ it("is read again after a write to its canister, unless invalidates leaves it out", async () => {
506
+ const { replica, client, canister } = mounted()
507
+ const observer = new QueryObserver(
508
+ client.queryClient,
509
+ client.queryOptions(canister, "address", "x", { update: "idempotent" })
510
+ )
511
+ const unsubscribe = observer.subscribe(() => {})
512
+ await vi.waitFor(() =>
513
+ expect(observer.getCurrentResult().data).toBe(`x:${client.caller()}`)
514
+ )
515
+ // Q10's default: every read of the canister written to, this one too.
516
+ await new MutationObserver(
517
+ client.queryClient,
518
+ client.mutationOptions(canister, "bump")
519
+ ).mutate(1n)
520
+ expect(requestsFor(replica, "address")).toHaveLength(2)
521
+ await new MutationObserver(
522
+ client.queryClient,
523
+ client.mutationOptions(canister, "bump", {
524
+ invalidates: [[canister, "one"]],
525
+ })
526
+ ).mutate(1n)
527
+ expect(requestsFor(replica, "address")).toHaveLength(2)
528
+ unsubscribe()
529
+ client.queryClient.unmount()
530
+ })
531
+
532
+ it("is refused before sending for a caller who is not signed in", async () => {
533
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
534
+ const client = clientAs(replica, "anonymous")
535
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
536
+ await expect(
537
+ client.queryClient.fetchQuery({
538
+ ...client.queryOptions(canister, "address", "x", {
539
+ update: "idempotent",
540
+ }),
541
+ retry: false,
542
+ })
543
+ ).rejects.toMatchObject({ kind: "unauthenticated" })
544
+ expect(canisterRequests(replica)).toEqual([])
545
+ })
546
+
547
+ it("retries only a failure that proves the update never got in", () => {
548
+ vi.stubGlobal("window", {})
549
+ const client = clientAs(replicaWith({}), alice)
550
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
551
+ const { retry } = client.queryOptions(canister, "address", "x", {
552
+ update: "idempotent",
553
+ })
554
+ const update = {
555
+ method: "address",
556
+ canisterId: SHAPES,
557
+ mode: "update",
558
+ accepted: false,
559
+ } as const
560
+ expect(
561
+ retry(
562
+ 0,
563
+ classifyError(
564
+ ProtocolError.fromCode(new HttpErrorCode(429, "slow down", [])),
565
+ update
566
+ )
567
+ )
568
+ ).toBe(true)
569
+ expect(
570
+ retry(
571
+ 0,
572
+ classifyError(
573
+ ProtocolError.fromCode(new HttpErrorCode(503, "busy", [])),
574
+ update
575
+ )
576
+ )
577
+ ).toBe(false)
578
+ expect(retry(0, classifyError(new TypeError("lost"), update))).toBe(false)
579
+ })
580
+ })
581
+
582
+ describe("mutationOptions", () => {
583
+ /** A ledger, a balance read observed on the client's QueryClient, and a transfer mutation. */
584
+ async function wallet(client?: Client) {
585
+ const balances = new Map<string, bigint>([[ALICE, 1_000_000n]])
586
+ const replica = replicaWith({ [LEDGER]: ledgerCanister(balances) })
587
+ const reader = clientAs(replica, alice)
588
+ const writer = client ?? reader
589
+ const ledger = reader.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
590
+ const balance = new QueryObserver(
591
+ reader.queryClient,
592
+ reader.queryOptions(ledger, "icrc1_balance_of", {
593
+ owner: ALICE,
594
+ subaccount: null,
595
+ })
596
+ )
597
+ const unsubscribe = balance.subscribe(() => {})
598
+ await vi.waitFor(() =>
599
+ expect(balance.getCurrentResult().data).toBe(1_000_000n)
600
+ )
601
+ return { replica, reader, writer, ledger, balance, unsubscribe }
602
+ }
603
+
604
+ const transfer = (amount: bigint): icrc1.TransferArg => ({
605
+ to: { owner: BOB, subaccount: null },
606
+ amount,
607
+ fee: null,
608
+ memo: null,
609
+ from_subaccount: null,
610
+ created_at_time: null,
611
+ })
612
+
613
+ const reads = (replica: ReturnType<typeof replicaWith>) =>
614
+ requestsFor(replica, "icrc1_balance_of").length
615
+
616
+ it("returns retry: false and, called with no onMutate run, calls the method as the caller current then", async () => {
617
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
618
+ const auth = createTestAuth({ seed: 1, signedIn: false })
619
+ const client = clientWithAuth(replica, auth)
620
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
621
+ const options = client.mutationOptions(canister, "address")
622
+ expect(options.retry).toBe(false)
623
+ expect(options.mutationKey).toEqual([
624
+ "ic-reactor",
625
+ client.network,
626
+ SHAPES,
627
+ "address",
628
+ ])
629
+ await expect(options.mutationFn("x")).rejects.toMatchObject({
630
+ kind: "unauthenticated",
631
+ })
632
+ expect(canisterRequests(replica)).toEqual([])
633
+ await auth.signIn()
634
+ await expect(options.mutationFn("x")).resolves.toBe(`x:${client.caller()}`)
635
+ })
636
+
637
+ it("invalidates the canister's reads after a lost reply, and the re-read shows the debit", async () => {
638
+ const { replica, writer, ledger, balance, unsubscribe } = await wallet()
639
+ const mutation = new MutationObserver(
640
+ writer.queryClient,
641
+ writer.mutationOptions(ledger, "icrc1_transfer")
642
+ )
643
+ replica.dropNextReply()
644
+ await expect(mutation.mutate(transfer(100n))).rejects.toMatchObject({
645
+ kind: "outcome_unknown",
646
+ mayHaveExecuted: true,
647
+ })
648
+ // onSettled has invalidated and waited for the active read to refetch.
649
+ expect(balance.getCurrentResult().data).toBe(1_000_000n - 100n - FEE)
650
+ expect(reads(replica)).toBe(2)
651
+ expect(requestsFor(replica, "icrc1_transfer")).toHaveLength(1)
652
+ unsubscribe()
653
+ })
654
+
655
+ it.each([
656
+ ["a success", 100n],
657
+ ["a canister_err", 5_000_000n],
658
+ ])("invalidates the canister's reads after %s", async (_label, amount) => {
659
+ const { replica, writer, ledger, unsubscribe } = await wallet()
660
+ const mutation = new MutationObserver(
661
+ writer.queryClient,
662
+ writer.mutationOptions(ledger, "icrc1_transfer")
663
+ )
664
+ await mutation.mutate(transfer(amount)).catch(() => undefined)
665
+ expect(reads(replica)).toBe(2)
666
+ unsubscribe()
667
+ })
668
+
669
+ it("does not invalidate after invalid_args, not_delivered or unauthenticated", async () => {
670
+ const { replica, writer, reader, ledger, balance, unsubscribe } =
671
+ await wallet()
672
+ const key = balance.options.queryKey
673
+ const mutation = new MutationObserver(
674
+ writer.queryClient,
675
+ writer.mutationOptions(ledger, "icrc1_transfer")
676
+ )
677
+ await expect(
678
+ mutation.mutate({ ...transfer(1n), amount: -1n })
679
+ ).rejects.toMatchObject({ kind: "invalid_args" })
680
+ replica.refuseNext(400)
681
+ await expect(mutation.mutate(transfer(1n))).rejects.toMatchObject({
682
+ kind: "not_delivered",
683
+ })
684
+ const signedOut = createClientSignedOut(replica)
685
+ const anonymousLedger = signedOut.canister<icrc1.Actor>(icrc1.actor, {
686
+ id: LEDGER,
687
+ })
688
+ // The anonymous client's own QueryClient holds a read of the ledger too.
689
+ const otherRead = signedOut.queryOptions(anonymousLedger, "icrc1_fee")
690
+ await signedOut.queryClient.fetchQuery(otherRead)
691
+ await expect(
692
+ new MutationObserver(
693
+ signedOut.queryClient,
694
+ signedOut.mutationOptions(anonymousLedger, "icrc1_transfer")
695
+ ).mutate(transfer(1n))
696
+ ).rejects.toMatchObject({ kind: "unauthenticated" })
697
+ expect(
698
+ signedOut.queryClient.getQueryState(otherRead.queryKey)?.isInvalidated
699
+ ).toBe(false)
700
+ expect(reader.queryClient.getQueryState(key)?.isInvalidated).toBe(false)
701
+ expect(reads(replica)).toBe(1)
702
+ unsubscribe()
703
+ })
704
+
705
+ it("does not invalidate after a write cancelled before it was sent", async () => {
706
+ const replica = replicaWith({ [SHAPES]: whoCanister(new Map()) })
707
+ const auth = createTestAuth({ seed: 1, signedIn: false })
708
+ // The identity of the write's caller arrives only after they signed out.
709
+ const asked = deferred()
710
+ const gate = deferred()
711
+ let slow = false
712
+ const slowAuth = withIdentity(auth, async () => {
713
+ const identity = await auth.getIdentity()
714
+ if (slow) {
715
+ asked.resolve()
716
+ await gate.promise
717
+ }
718
+ return identity
719
+ })
720
+ const client = clientWithAuth(replica, slowAuth)
721
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
722
+ // A read the write would invalidate, made before anyone signed in: the
723
+ // sign-out below keeps the anonymous reads.
724
+ const read = client.queryOptions(canister, "one", 1n)
725
+ await client.queryClient.fetchQuery(read)
726
+ await client.signIn()
727
+
728
+ slow = true
729
+ const write = new MutationObserver(
730
+ client.queryClient,
731
+ client.mutationOptions(canister, "address")
732
+ ).mutate("x")
733
+ await asked.promise
734
+ await auth.signOut()
735
+ gate.resolve()
736
+ await expect(write).rejects.toMatchObject({
737
+ kind: "cancelled",
738
+ code: "caller_changed",
739
+ mayHaveExecuted: false,
740
+ })
741
+ expect(requestsFor(replica, "address")).toEqual([])
742
+ expect(client.queryClient.getQueryState(read.queryKey)?.isInvalidated).toBe(
743
+ false
744
+ )
745
+ expect(requestsFor(replica, "one")).toHaveLength(1)
746
+ })
747
+
748
+ it("invalidates only what invalidates lists, and nothing for []", async () => {
749
+ const { replica, writer, ledger, unsubscribe } = await wallet()
750
+ const fee = writer.queryOptions(ledger, "icrc1_fee")
751
+ await writer.queryClient.fetchQuery(fee)
752
+ const only = new MutationObserver(
753
+ writer.queryClient,
754
+ writer.mutationOptions(ledger, "icrc1_transfer", {
755
+ invalidates: [[ledger, "icrc1_fee"]],
756
+ })
757
+ )
758
+ await only.mutate(transfer(1n))
759
+ expect(writer.queryClient.getQueryState(fee.queryKey)?.isInvalidated).toBe(
760
+ true
761
+ )
762
+ expect(reads(replica)).toBe(1)
763
+ const none = new MutationObserver(
764
+ writer.queryClient,
765
+ writer.mutationOptions(ledger, "icrc1_transfer", { invalidates: [] })
766
+ )
767
+ await none.mutate(transfer(1n))
768
+ expect(reads(replica)).toBe(1)
769
+ unsubscribe()
770
+ })
771
+
772
+ it("invalidates the reads of every caller, certified or not", async () => {
773
+ const holds = new Map<string, Promise<void>>()
774
+ const replica = replicaWith({ [SHAPES]: whoCanister(holds) })
775
+ const auth = createTestAuth({ seed: 1, signedIn: false })
776
+ const client = clientWithAuth(replica, auth)
777
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
778
+ const certified = client.canister<shapes.Actor>(shapes.actor, {
779
+ id: SHAPES,
780
+ certified: true,
781
+ })
782
+ // Reads by nobody signed in, which a sign-in keeps, and by A.
783
+ const anonymous = client.queryOptions(canister, "who", undefined)
784
+ const certifiedAnonymous = client.queryOptions(certified, "one", 1n)
785
+ await client.queryClient.fetchQuery(anonymous)
786
+ await client.queryClient.fetchQuery(certifiedAnonymous)
787
+ await client.signIn()
788
+ const asA = client.queryOptions(canister, "who", undefined)
789
+ const certifiedAsA = client.queryOptions(certified, "one", 1n)
790
+ await client.queryClient.fetchQuery(asA)
791
+ await client.queryClient.fetchQuery(certifiedAsA)
792
+ // And one of B's, as a persister restores it into the cache.
793
+ const b = createTestAuth({ seed: 2 }).getPrincipal()!.toText()
794
+ const asB = [...asA.queryKey.slice(0, 2), b, ...asA.queryKey.slice(3)]
795
+ client.queryClient.setQueryData(asB, b)
796
+ await client
797
+ .mutationOptions(canister, "note")
798
+ .onSettled(undefined, null, "x")
799
+ for (const queryKey of [
800
+ anonymous.queryKey,
801
+ certifiedAnonymous.queryKey,
802
+ asA.queryKey,
803
+ certifiedAsA.queryKey,
804
+ asB,
805
+ ]) {
806
+ expect(client.queryClient.getQueryState(queryKey)?.isInvalidated).toBe(
807
+ true
808
+ )
809
+ }
810
+ })
811
+
812
+ // An app that nests its own QueryClientProvider inside the client's keeps
813
+ // its reads, and runs its writes, on its own QueryClient (#903).
814
+ it("invalidates the reads on the QueryClient the mutation ran on, and on the client's own", async () => {
815
+ const { replica, reader, ledger, balance, unsubscribe } = await wallet()
816
+ const app = new QueryClient()
817
+ const account = { owner: ALICE, subaccount: null }
818
+ const appBalance = new QueryObserver(
819
+ app,
820
+ reader.queryOptions(ledger, "icrc1_balance_of", account)
821
+ )
822
+ const unsubscribeApp = appBalance.subscribe(() => {})
823
+ await vi.waitFor(() =>
824
+ expect(appBalance.getCurrentResult().data).toBe(1_000_000n)
825
+ )
826
+ const mutation = new MutationObserver(
827
+ app,
828
+ reader.mutationOptions(ledger, "icrc1_transfer")
829
+ )
830
+
831
+ await mutation.mutate(transfer(100n))
832
+
833
+ // onSettled waited for both active reads to refetch. A TanStack Query
834
+ // older than 5.89 does not tell onSettled which QueryClient the run was
835
+ // on, so only the client's own reads are invalidated there.
836
+ const handsClient = await sharesRunContext()
837
+ expect(balance.getCurrentResult().data).toBe(1_000_000n - 100n - FEE)
838
+ expect(appBalance.getCurrentResult().data).toBe(
839
+ handsClient ? 1_000_000n - 100n - FEE : 1_000_000n
840
+ )
841
+ expect(reads(replica)).toBe(handsClient ? 4 : 3)
842
+ unsubscribeApp()
843
+ unsubscribe()
844
+ })
845
+
846
+ /**
847
+ * A client on a local page, where the ic_env cookie is trusted, and a
848
+ * `{ name: "backend" }` canister of it. The cookie is read afresh every
849
+ * time, and `redeploy()` moves the name from SHAPES to REDEPLOYED, as a
850
+ * local redeploy rewrites the cookie. `bump` waits for `hold` at SHAPES
851
+ * and at BY_ID, a shapes canister the name never points to.
852
+ */
853
+ function redeployable(hold?: Promise<void>) {
854
+ let deployed = SHAPES
855
+ stubPage("http://localhost:5173")
856
+ vi.stubGlobal("document", {
857
+ get cookie() {
858
+ return icEnvCookie({ "PUBLIC_CANISTER_ID:backend": deployed })
859
+ },
860
+ })
861
+ const backendAt = (wait?: Promise<void>) =>
862
+ serve<shapes.Actor>(shapes.actor, {
863
+ one: ([n]) => n + 1n,
864
+ bump: async ([n]) => {
865
+ await wait
866
+ return { tag: "ok", value: n + 1n }
867
+ },
868
+ })
869
+ const replica = replicaWith({
870
+ [SHAPES]: backendAt(hold),
871
+ [REDEPLOYED]: backendAt(),
872
+ [BY_ID]: backendAt(hold),
873
+ })
874
+ const client = clientAs(replica, alice)
875
+ const backend = client.canister<shapes.Actor>(shapes.actor, {
876
+ name: "backend",
877
+ })
878
+ const redeploy = () => {
879
+ deployed = REDEPLOYED
880
+ }
881
+ return { replica, client, backend, redeploy }
882
+ }
883
+
884
+ type BumpOptions = CanisterMutationOptions<bigint, bigint, string>
885
+
886
+ /** What an app hands `useMutation` or a `MutationObserver` for `bump`, with an onMutate of its own or not. */
887
+ type AppBumpOptions = MutationObserverOptions<
888
+ bigint,
889
+ ReactorError<string>,
890
+ bigint,
891
+ unknown
892
+ >
893
+
894
+ /**
895
+ * Writes `bump` to the `{ name }` canister with the options `compose`
896
+ * returns, and moves the name to another canister while the update is in
897
+ * flight. The write must reach the first canister, whose cached reads it
898
+ * invalidates, and leave the reads of the new one alone.
899
+ */
900
+ async function writeAcrossRedeploy(
901
+ compose: (options: BumpOptions) => AppBumpOptions
902
+ ) {
903
+ const held = deferred()
904
+ const { replica, client, backend, redeploy } = redeployable(held.promise)
905
+ const before = client.queryOptions(backend, "one", 1n)
906
+ expect(before.queryKey[3]).toBe(SHAPES)
907
+ await client.queryClient.fetchQuery(before)
908
+
909
+ const write = new MutationObserver(
910
+ client.queryClient,
911
+ compose(client.mutationOptions(backend, "bump"))
912
+ ).mutate(1n)
913
+ await vi.waitFor(() => expect(requestsFor(replica, "bump")).toHaveLength(1))
914
+ // The update is in flight at the first canister; a redeploy gives the
915
+ // name another one, and a read made now goes there.
916
+ redeploy()
917
+ const after = client.queryOptions(backend, "one", 1n)
918
+ expect(after.queryKey[3]).toBe(REDEPLOYED)
919
+ await client.queryClient.fetchQuery(after)
920
+ held.resolve()
921
+ await expect(write).resolves.toBe(2n)
922
+
923
+ expect(requestsFor(replica, "bump")).toMatchObject([{ canisterId: SHAPES }])
924
+ // The reads of the canister written to are stale; the other's are not.
925
+ expect(
926
+ client.queryClient.getQueryState(before.queryKey)?.isInvalidated
927
+ ).toBe(true)
928
+ expect(
929
+ client.queryClient.getQueryState(after.queryKey)?.isInvalidated
930
+ ).toBe(false)
931
+ }
932
+
933
+ it.each([
934
+ ["as given", (options: BumpOptions) => options],
935
+ [
936
+ "under an onMutate of the app's that spreads the given one's result into its own",
937
+ (options: BumpOptions) => ({
938
+ ...options,
939
+ onMutate: (vars: bigint, context?: unknown) => ({
940
+ ...options.onMutate(vars, context),
941
+ previous: "the app's own context",
942
+ }),
943
+ }),
944
+ ],
945
+ ])(
946
+ "invalidates the canister a { name } write went to when the cookie moves the name before the write settles, with the options %s",
947
+ (_label, compose) => writeAcrossRedeploy(compose)
948
+ )
949
+
950
+ it("keeps a { name } write and its invalidation on one canister when an onMutate of the app's replaces the given one", async ({
951
+ skip,
952
+ }) => {
953
+ if (!(await sharesRunContext())) {
954
+ skip(
955
+ "this TanStack Query does not pass mutationFn and onSettled the run's context, so each resolves the canister when it runs"
956
+ )
957
+ }
958
+ // TanStack's optimistic-update recipe, spread over the options: the
959
+ // app's onMutate returns its own context and never calls the given one.
960
+ await writeAcrossRedeploy((options) => ({
961
+ ...options,
962
+ onMutate: () => ({ previous: "the app's own context" }),
963
+ }))
964
+ })
965
+
966
+ it("invalidates the canister a { name } in invalidates named when the write started, though the cookie moves the name before the write settles", async () => {
967
+ const held = deferred()
968
+ const { replica, client, backend, redeploy } = redeployable(held.promise)
969
+ const writer = client.canister<shapes.Actor>(shapes.actor, { id: BY_ID })
970
+ const before = client.queryOptions(backend, "one", 1n)
971
+ expect(before.queryKey[3]).toBe(SHAPES)
972
+ await client.queryClient.fetchQuery(before)
973
+
974
+ // The write goes to a canister reached by its id, and names the reads it
975
+ // changes through the { name }, the only target that a redeploy moves.
976
+ const write = new MutationObserver(
977
+ client.queryClient,
978
+ client.mutationOptions(writer, "bump", { invalidates: [backend] })
979
+ ).mutate(1n)
980
+ await vi.waitFor(() => expect(requestsFor(replica, "bump")).toHaveLength(1))
981
+ redeploy()
982
+ const after = client.queryOptions(backend, "one", 1n)
983
+ expect(after.queryKey[3]).toBe(REDEPLOYED)
984
+ await client.queryClient.fetchQuery(after)
985
+ held.resolve()
986
+ await expect(write).resolves.toBe(2n)
987
+
988
+ expect(requestsFor(replica, "bump")).toMatchObject([{ canisterId: BY_ID }])
989
+ // The listed name pointed at SHAPES when the write started: its reads
990
+ // there are stale, and the reads of where it points now are not.
991
+ expect(
992
+ client.queryClient.getQueryState(before.queryKey)?.isInvalidated
993
+ ).toBe(true)
994
+ expect(
995
+ client.queryClient.getQueryState(after.queryKey)?.isInvalidated
996
+ ).toBe(false)
997
+ })
998
+
999
+ it("sends a { name } write that waited offline to the canister resolved when it started, whose reads it invalidates", async ({
1000
+ skip,
1001
+ }) => {
1002
+ if (!(await sharesRunContext())) {
1003
+ skip(
1004
+ "this TanStack Query does not pass mutationFn the run's context, so mutationFn resolves the canister when it starts"
1005
+ )
1006
+ }
1007
+ const { replica, client, backend, redeploy } = redeployable()
1008
+ const read = client.queryOptions(backend, "one", 1n)
1009
+ await client.queryClient.fetchQuery(read)
1010
+
1011
+ // Offline, the write is paused after onMutate ran and before mutationFn
1012
+ // does; the name moves to another canister in between.
1013
+ onlineManager.setOnline(false)
1014
+ const observer = new MutationObserver(
1015
+ client.queryClient,
1016
+ client.mutationOptions(backend, "bump")
1017
+ )
1018
+ const write = observer.mutate(1n)
1019
+ await vi.waitFor(() => {
1020
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1021
+ expect(observer.getCurrentResult().context).toBeDefined()
1022
+ })
1023
+ redeploy()
1024
+ onlineManager.setOnline(true)
1025
+ void client.queryClient.resumePausedMutations()
1026
+ await expect(write).resolves.toBe(2n)
1027
+
1028
+ expect(requestsFor(replica, "bump")).toMatchObject([{ canisterId: SHAPES }])
1029
+ expect(client.queryClient.getQueryState(read.queryKey)?.isInvalidated).toBe(
1030
+ true
1031
+ )
1032
+ })
1033
+
1034
+ /**
1035
+ * A shapes canister whose `address` answers with its sender, held back
1036
+ * while `hold` is set, and a client on it signed in as seed 1.
1037
+ */
1038
+ function addressBook() {
1039
+ let hold: Promise<void> | undefined
1040
+ const calls = { before: undefined as (() => void) | undefined }
1041
+ const replica = replicaWith({
1042
+ [SHAPES]: serve<shapes.Actor>(shapes.actor, {
1043
+ address: async ([seed], { caller }) => {
1044
+ await hold
1045
+ calls.before?.()
1046
+ return `${seed}:${caller}`
1047
+ },
1048
+ }),
1049
+ })
1050
+ const auth = createTestAuth({ seed: 1 })
1051
+ const client = clientWithAuth(replica, auth)
1052
+ const canister = client.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
1053
+ const holdNext = () => {
1054
+ const held = deferred()
1055
+ hold = held.promise
1056
+ return held
1057
+ }
1058
+ return { replica, auth, client, canister, holdNext, calls }
1059
+ }
1060
+
1061
+ const callerChanged = {
1062
+ kind: "cancelled",
1063
+ code: "caller_changed",
1064
+ mayHaveExecuted: false,
1065
+ }
1066
+
1067
+ it("refuses a write paused offline when the account is switched before it is sent, and sends nothing", async () => {
1068
+ const { replica, auth, client, canister } = addressBook()
1069
+ onlineManager.setOnline(false)
1070
+ const observer = new MutationObserver(
1071
+ client.queryClient,
1072
+ client.mutationOptions(canister, "address")
1073
+ )
1074
+ const write = observer.mutate("x")
1075
+ write.catch(() => undefined)
1076
+ await vi.waitFor(() =>
1077
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1078
+ )
1079
+ auth.switchTo(2)
1080
+ onlineManager.setOnline(true)
1081
+ void client.queryClient.resumePausedMutations()
1082
+ await expect(write).rejects.toMatchObject(callerChanged)
1083
+ expect(requestsFor(replica, "address")).toEqual([])
1084
+ })
1085
+
1086
+ it("sends a write paused offline as its caller when nobody switched meanwhile", async () => {
1087
+ const { client, canister } = addressBook()
1088
+ const clickedAs = client.caller()
1089
+ onlineManager.setOnline(false)
1090
+ const observer = new MutationObserver(
1091
+ client.queryClient,
1092
+ client.mutationOptions(canister, "address")
1093
+ )
1094
+ const write = observer.mutate("x")
1095
+ await vi.waitFor(() =>
1096
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1097
+ )
1098
+ onlineManager.setOnline(true)
1099
+ void client.queryClient.resumePausedMutations()
1100
+ await expect(write).resolves.toBe(`x:${clickedAs}`)
1101
+ })
1102
+
1103
+ it("refuses a write queued behind its scope when the account is switched before its turn, and sends nothing", async () => {
1104
+ const { replica, auth, client, canister, holdNext } = addressBook()
1105
+ const clickedAs = client.caller()
1106
+ const options = {
1107
+ ...client.mutationOptions(canister, "address"),
1108
+ scope: { id: "address" },
1109
+ }
1110
+ const held = holdNext()
1111
+ const first = new MutationObserver(client.queryClient, options).mutate("a")
1112
+ await vi.waitFor(() =>
1113
+ expect(requestsFor(replica, "address")).toHaveLength(1)
1114
+ )
1115
+ const queued = new MutationObserver(client.queryClient, options)
1116
+ const second = queued.mutate("b")
1117
+ second.catch(() => undefined)
1118
+ await vi.waitFor(() =>
1119
+ expect(queued.getCurrentResult().isPaused).toBe(true)
1120
+ )
1121
+ auth.switchTo(2)
1122
+ held.resolve()
1123
+ await expect(first).resolves.toBe(`a:${clickedAs}`)
1124
+ await expect(second).rejects.toMatchObject(callerChanged)
1125
+ expect(requestsFor(replica, "address")).toHaveLength(1)
1126
+ })
1127
+
1128
+ /** Pauses `observer`'s write offline, runs `between`, and comes back online. */
1129
+ async function pausedOffline(
1130
+ client: Client,
1131
+ observer: MutationObserver<unknown, unknown, string, unknown>,
1132
+ between: () => void
1133
+ ) {
1134
+ onlineManager.setOnline(false)
1135
+ const write = observer.mutate("x")
1136
+ write.catch(() => undefined)
1137
+ await vi.waitFor(() =>
1138
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1139
+ )
1140
+ between()
1141
+ onlineManager.setOnline(true)
1142
+ void client.queryClient.resumePausedMutations()
1143
+ return write
1144
+ }
1145
+
1146
+ /** TanStack's optimistic-update recipe: an onMutate of the app's that returns its own context and never calls the given one. */
1147
+ const replacingOnMutate = (client: Client, canister: unknown) =>
1148
+ new MutationObserver<unknown, unknown, string, unknown>(
1149
+ client.queryClient,
1150
+ {
1151
+ ...client.mutationOptions(
1152
+ canister as Parameters<Client["mutationOptions"]>[0],
1153
+ "address" as never
1154
+ ),
1155
+ onMutate: () => ({ previous: "the app's own context" }),
1156
+ } as never
1157
+ )
1158
+
1159
+ it("refuses a write paused offline across an account switch when an onMutate of the app's replaces the given one", async () => {
1160
+ const { replica, auth, client, canister } = addressBook()
1161
+ const write = pausedOffline(
1162
+ client,
1163
+ replacingOnMutate(client, canister),
1164
+ () => auth.switchTo(2)
1165
+ )
1166
+ await expect(write).rejects.toMatchObject(callerChanged)
1167
+ expect(requestsFor(replica, "address")).toEqual([])
1168
+ })
1169
+
1170
+ it("sends a write paused offline as its caller when an onMutate of the app's replaces the given one", async () => {
1171
+ const { client, canister } = addressBook()
1172
+ const clickedAs = client.caller()
1173
+ const write = pausedOffline(
1174
+ client,
1175
+ replacingOnMutate(client, canister),
1176
+ () => undefined
1177
+ )
1178
+ await expect(write).resolves.toBe(`x:${clickedAs}`)
1179
+ })
1180
+
1181
+ it("refuses the retry of a write when the account is switched after the first attempt failed, and sends it no more", async () => {
1182
+ const { replica, auth, client, canister, calls } = addressBook()
1183
+ calls.before = () => {
1184
+ calls.before = undefined
1185
+ auth.switchTo(2)
1186
+ throw new Error("trapped")
1187
+ }
1188
+ const write = new MutationObserver(client.queryClient, {
1189
+ ...client.mutationOptions(canister, "address"),
1190
+ // An app's own retry, against the advice.
1191
+ retry: 1,
1192
+ retryDelay: 0,
1193
+ }).mutate("x")
1194
+ await expect(write).rejects.toMatchObject(callerChanged)
1195
+ expect(requestsFor(replica, "address")).toHaveLength(1)
1196
+ })
1197
+
1198
+ it("refuses a write TanStack restored from a persister, whose caller is not known, and sends nothing", async () => {
1199
+ const { replica, client, canister } = addressBook()
1200
+ const options = client.mutationOptions(canister, "address")
1201
+ const observer = new MutationObserver(client.queryClient, options)
1202
+ onlineManager.setOnline(false)
1203
+ observer.mutate("x").catch(() => undefined)
1204
+ await vi.waitFor(() =>
1205
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1206
+ )
1207
+ // What a persister writes and, after a reload, reads back.
1208
+ const persisted = dehydrate(client.queryClient)
1209
+ client.queryClient.getMutationCache().clear()
1210
+ // A restored mutation takes its mutationFn from the defaults.
1211
+ client.queryClient.setMutationDefaults(options.mutationKey, {
1212
+ mutationFn: options.mutationFn as never,
1213
+ })
1214
+ hydrate(client.queryClient, persisted)
1215
+ onlineManager.setOnline(true)
1216
+ await client.queryClient.resumePausedMutations()
1217
+ const [restored] = client.queryClient.getMutationCache().getAll()
1218
+ await vi.waitFor(() =>
1219
+ expect(restored?.state.error).toMatchObject(callerChanged)
1220
+ )
1221
+ expect(requestsFor(replica, "address")).toEqual([])
1222
+ })
1223
+
1224
+ it("refuses a write whose mutationFn gets no run context (TanStack before 5.89, another QueryClient) unless the caller onMutate read for its options is the live one", async () => {
1225
+ const { replica, auth, client, canister } = addressBook()
1226
+ const clickedAs = client.caller()
1227
+ const options = client.mutationOptions(canister, "address")
1228
+ // TanStack before 5.89 calls both with the argument alone.
1229
+ options.onMutate("x")
1230
+ auth.switchTo(2)
1231
+ await expect(options.mutationFn("x")).rejects.toMatchObject(callerChanged)
1232
+ // A run whose onMutate read nothing for its options (a later render
1233
+ // handed it new ones): its caller is not known.
1234
+ await expect(
1235
+ options.mutationFn.call({ ...options }, "x")
1236
+ ).rejects.toMatchObject(callerChanged)
1237
+ expect(requestsFor(replica, "address")).toEqual([])
1238
+ auth.switchTo(1)
1239
+ options.onMutate("y")
1240
+ await expect(options.mutationFn("y")).resolves.toBe(`y:${clickedAs}`)
1241
+ })
1242
+
1243
+ it("never sends a write with no run context as the caller of a run whose mutationFn never ran", async () => {
1244
+ const { replica, auth, client, canister } = addressBook()
1245
+ const options = client.mutationOptions(canister, "address")
1246
+ // Two runs of "z" as TanStack before 5.89 makes them through two
1247
+ // observers, each with its own options object. The first, started as the first account, never gets
1248
+ // to mutationFn (its mutation was removed).
1249
+ const abandoned = { ...options }
1250
+ options.onMutate.call(abandoned, "z")
1251
+ auth.switchTo(2)
1252
+ const second = client.caller()
1253
+ const run = { ...options }
1254
+ options.onMutate.call(run, "z")
1255
+ // Back to the first account before the second account's run is sent.
1256
+ auth.switchTo(1)
1257
+ await expect(options.mutationFn.call(run, "z")).rejects.toMatchObject(
1258
+ callerChanged
1259
+ )
1260
+ expect(requestsFor(replica, "address")).toEqual([])
1261
+ auth.switchTo(2)
1262
+ await expect(options.mutationFn.call(run, "z")).resolves.toBe(`z:${second}`)
1263
+ })
1264
+
1265
+ it("never sends a run on another QueryClient (no run context) as the caller of a run of the client's own cache with the same argument", async () => {
1266
+ const { replica, auth, client, canister } = addressBook()
1267
+ const alice = client.caller()
1268
+ const options = client.mutationOptions(canister, "address")
1269
+ // Alice's write of "x", paused offline in the client's own cache.
1270
+ onlineManager.setOnline(false)
1271
+ const observer = new MutationObserver(client.queryClient, options)
1272
+ observer.mutate("x").catch(() => undefined)
1273
+ await vi.waitFor(() =>
1274
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1275
+ )
1276
+ onlineManager.setOnline(true)
1277
+ // Bob's write of "x" through the same options on another QueryClient,
1278
+ // as TanStack before 5.89 runs it: both functions called as methods of
1279
+ // its observer's options object, with the argument alone.
1280
+ auth.switchTo(2)
1281
+ const bobsRun = { ...options }
1282
+ options.onMutate.call(bobsRun, "x")
1283
+ // Alice is back before Bob's run is sent.
1284
+ auth.switchTo(1)
1285
+ expect(client.caller()).toBe(alice)
1286
+ await expect(options.mutationFn.call(bobsRun, "x")).rejects.toMatchObject(
1287
+ callerChanged
1288
+ )
1289
+ expect(requestsFor(replica, "address")).toEqual([])
1290
+ })
1291
+
1292
+ it("never sends a write with no run context as the caller of a settled run that shares its options (one observer, TanStack before 5.89)", async () => {
1293
+ const { replica, auth, client, canister } = addressBook()
1294
+ const alice = client.caller()
1295
+ const options = client.mutationOptions(canister, "address")
1296
+ const observer = new MutationObserver(client.queryClient, options)
1297
+ // Alice's write, settled and still cached.
1298
+ await expect(observer.mutate("a")).resolves.toBe(`a:${alice}`)
1299
+ // Bob's write through the same observer, held back offline: TanStack
1300
+ // builds it with the same options object as Alice's.
1301
+ auth.switchTo(2)
1302
+ onlineManager.setOnline(false)
1303
+ observer.mutate("b").catch(() => undefined)
1304
+ await vi.waitFor(() =>
1305
+ expect(observer.getCurrentResult().isPaused).toBe(true)
1306
+ )
1307
+ const runs = client.queryClient.getMutationCache().getAll()
1308
+ expect(runs).toHaveLength(2)
1309
+ expect(runs[0]?.options).toBe(runs[1]?.options)
1310
+ onlineManager.setOnline(true)
1311
+ // Alice is back when Bob's run is sent as TanStack before 5.89 sends it:
1312
+ // mutationFn called on the shared options, with the argument alone.
1313
+ auth.switchTo(1)
1314
+ await expect(
1315
+ options.mutationFn.call(runs[1]?.options, "b")
1316
+ ).rejects.toMatchObject(callerChanged)
1317
+ // Only Alice's write went out.
1318
+ expect(requestsFor(replica, "address")).toHaveLength(1)
1319
+ // With Bob current, his run goes out as him: Alice's settled run is not
1320
+ // one it may run for.
1321
+ auth.switchTo(2)
1322
+ const bob = client.caller()
1323
+ await expect(options.mutationFn.call(runs[1]?.options, "b")).resolves.toBe(
1324
+ `b:${bob}`
1325
+ )
1326
+ })
1327
+
1328
+ it("refuses a write with no run context whose observer's pending runs started as different callers (TanStack before 5.89)", async () => {
1329
+ const { replica, auth, client, canister } = addressBook()
1330
+ const options = client.mutationOptions(canister, "address")
1331
+ const observer = new MutationObserver(client.queryClient, options)
1332
+ // Alice's write, then Bob's, both held back offline.
1333
+ onlineManager.setOnline(false)
1334
+ observer.mutate("a").catch(() => undefined)
1335
+ auth.switchTo(2)
1336
+ observer.mutate("b").catch(() => undefined)
1337
+ const runs = client.queryClient.getMutationCache().getAll()
1338
+ await vi.waitFor(() =>
1339
+ expect(runs.every((run) => run.state.isPaused)).toBe(true)
1340
+ )
1341
+ onlineManager.setOnline(true)
1342
+ // Called on the options both share, mutationFn cannot tell which of them
1343
+ // it runs for, so it sends neither, whoever is current.
1344
+ auth.switchTo(1)
1345
+ await expect(
1346
+ options.mutationFn.call(runs[0]?.options, "a")
1347
+ ).rejects.toMatchObject(callerChanged)
1348
+ expect(requestsFor(replica, "address")).toEqual([])
1349
+ })
1350
+
1351
+ it("refuses writes with no run context whose options' pending runs started as different callers, until they settle (another QueryClient)", async () => {
1352
+ const { replica, auth, client, canister } = addressBook()
1353
+ const alice = client.caller()
1354
+ const options = client.mutationOptions(canister, "address")
1355
+ // Two runs of one observer on another QueryClient, as TanStack before
1356
+ // 5.89 runs them: every function called on the observer's one options
1357
+ // object, with the argument alone.
1358
+ const shared = { ...options }
1359
+ options.onMutate.call(shared, "a")
1360
+ auth.switchTo(2)
1361
+ options.onMutate.call(shared, "b")
1362
+ // Neither run can be told from the other, so neither is sent, whoever
1363
+ // is current.
1364
+ await expect(options.mutationFn.call(shared, "b")).rejects.toMatchObject(
1365
+ callerChanged
1366
+ )
1367
+ auth.switchTo(1)
1368
+ await expect(options.mutationFn.call(shared, "a")).rejects.toMatchObject(
1369
+ callerChanged
1370
+ )
1371
+ expect(requestsFor(replica, "address")).toEqual([])
1372
+ // Once both settled, a new run is sent as its caller.
1373
+ await options.onSettled.call(shared, undefined, null, "a", undefined)
1374
+ await options.onSettled.call(shared, undefined, null, "b", undefined)
1375
+ options.onMutate.call(shared, "c")
1376
+ await expect(options.mutationFn.call(shared, "c")).resolves.toBe(
1377
+ `c:${alice}`
1378
+ )
1379
+ })
1380
+
1381
+ it("reads no caller, and builds no auth, for a mutation of the app's on the client's QueryClient", async () => {
1382
+ let built = 0
1383
+ // In a page, where the client builds its auth on first use.
1384
+ vi.stubGlobal("window", {
1385
+ location: { origin: "https://app.example.com", protocol: "https:" },
1386
+ })
1387
+ const client = createClient({
1388
+ network: "local",
1389
+ auth: () => {
1390
+ built += 1
1391
+ throw new Error("the auth could not be built")
1392
+ },
1393
+ })
1394
+ const unrelated = new MutationObserver(client.queryClient, {
1395
+ mutationFn: (n: number) => Promise.resolve(n + 1),
1396
+ })
1397
+ await expect(unrelated.mutate(1)).resolves.toBe(2)
1398
+ expect(built).toBe(0)
1399
+ })
1400
+
1401
+ it("throws a TypeError for an unknown third argument or a canister of another client", () => {
1402
+ const client = clientAs(replicaWith({}), alice)
1403
+ const other = clientAs(replicaWith({}), alice)
1404
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
1405
+ const foreign = other.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
1406
+ expect(() =>
1407
+ client.mutationOptions(ledger, "icrc1_transfer", {
1408
+ retry: 3,
1409
+ } as unknown as { invalidates: [] })
1410
+ ).toThrow(/never retried/)
1411
+ expect(() =>
1412
+ client.mutationOptions(ledger, "icrc1_transfer", {
1413
+ invalidates: [foreign],
1414
+ })
1415
+ ).toThrow(/another client/)
1416
+ expect(() =>
1417
+ client.mutationOptions(ledger, "icrc1_transfer", {
1418
+ invalidates: [[ledger, "nope"]],
1419
+ })
1420
+ ).toThrow(/has no method "nope"/)
1421
+ })
1422
+ })
1423
+
1424
+ describe("timeoutMs", () => {
1425
+ // #875: a bound on how long one call waits, for reads and mutations.
1426
+ const within = <T>(call: Promise<T>) =>
1427
+ Promise.race([call, sleep(2_000).then(() => "still waiting")])
1428
+
1429
+ /** A ledger whose transfers and balances never answer. */
1430
+ const held = () => {
1431
+ const fake = ledgerCanister(new Map([[ALICE as string, 1_000_000n]]))
1432
+ const replica = replicaWith({
1433
+ [LEDGER]: {
1434
+ query: (method, arg, context) =>
1435
+ method === "icrc1_balance_of"
1436
+ ? new Promise(() => {})
1437
+ : fake.query!(method, arg, context),
1438
+ update: () => new Promise(() => {}),
1439
+ },
1440
+ })
1441
+ return { replica, client: clientAs(replica, alice) }
1442
+ }
1443
+ const transfer = (amount: bigint): icrc1.TransferArg => ({
1444
+ to: { owner: BOB, subaccount: null },
1445
+ amount,
1446
+ fee: null,
1447
+ memo: null,
1448
+ from_subaccount: null,
1449
+ created_at_time: 1_700_000_000_000_000_000n,
1450
+ })
1451
+
1452
+ it.each([
1453
+ ["its own timeoutMs", {}, { timeoutMs: 50 }],
1454
+ ["the canister's timeoutMs", { timeoutMs: 50 }, {}],
1455
+ [
1456
+ "its own timeoutMs over the canister's",
1457
+ { timeoutMs: 600_000 },
1458
+ { timeoutMs: 50 },
1459
+ ],
1460
+ ] as const)(
1461
+ "rejects a mutation whose request went out outcome_unknown with %s, and offers it to resendOf",
1462
+ async (_, target, options) => {
1463
+ const { replica, client } = held()
1464
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, {
1465
+ id: LEDGER,
1466
+ ...target,
1467
+ })
1468
+ const mutation = new MutationObserver(
1469
+ client.queryClient,
1470
+ client.mutationOptions(ledger, "icrc1_transfer", options)
1471
+ )
1472
+ const error = await within(mutation.mutate(transfer(1n))).catch(
1473
+ (failure: unknown) => failure
1474
+ )
1475
+ expect(error).toMatchObject({
1476
+ kind: "outcome_unknown",
1477
+ mayHaveExecuted: true,
1478
+ code: "timeout",
1479
+ })
1480
+ expect(mutation.getCurrentResult().error).toBe(error)
1481
+ expect(
1482
+ client.resendOf(error, ledger, "icrc1_transfer", {
1483
+ dedupedBy: (arg) => arg.created_at_time,
1484
+ })
1485
+ ).toBeDefined()
1486
+ expect(requestsFor(replica, "icrc1_transfer")).toHaveLength(1)
1487
+ }
1488
+ )
1489
+
1490
+ it("keeps a mutation re-sending an offered write outcome_unknown when it times out before it goes out", async () => {
1491
+ const { replica, client } = held()
1492
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
1493
+ const mutation = new MutationObserver(
1494
+ client.queryClient,
1495
+ client.mutationOptions(ledger, "icrc1_transfer", { timeoutMs: 50 })
1496
+ )
1497
+ const first = await within(mutation.mutate(transfer(1n))).catch(
1498
+ (failure: unknown) => failure
1499
+ )
1500
+ const dedupedBy = (arg: icrc1.TransferArg) => arg.created_at_time
1501
+ const offer = client.resendOf(first, ledger, "icrc1_transfer", {
1502
+ dedupedBy,
1503
+ })
1504
+ expect(offer).toBeDefined()
1505
+ replica.refuseNext(429)
1506
+ const again = await within(mutation.mutate(offer!.arg)).catch(
1507
+ (failure: unknown) => failure
1508
+ )
1509
+ expect(again).toMatchObject({
1510
+ kind: "outcome_unknown",
1511
+ mayHaveExecuted: true,
1512
+ code: "timeout",
1513
+ })
1514
+ expect(
1515
+ client.resendOf(again, ledger, "icrc1_transfer", { dedupedBy })
1516
+ ).toBeDefined()
1517
+ expect(requestsFor(replica, "icrc1_transfer")).toHaveLength(2)
1518
+ })
1519
+
1520
+ it("rejects a read of a canister with timeoutMs cancelled, with nothing executed", async () => {
1521
+ const { replica, client } = held()
1522
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, {
1523
+ id: LEDGER,
1524
+ timeoutMs: 50,
1525
+ })
1526
+ await expect(
1527
+ within(
1528
+ client.queryClient.fetchQuery(
1529
+ client.queryOptions(ledger, "icrc1_balance_of", {
1530
+ owner: ALICE,
1531
+ subaccount: null,
1532
+ })
1533
+ )
1534
+ )
1535
+ ).rejects.toMatchObject({
1536
+ kind: "cancelled",
1537
+ mayHaveExecuted: false,
1538
+ code: "timeout",
1539
+ })
1540
+ expect(requestsFor(replica, "icrc1_balance_of")).toHaveLength(1)
1541
+ })
1542
+
1543
+ it.each([0, 0.5, Number.NaN, 2 ** 31, "50"])(
1544
+ "throws a TypeError for a mutation's timeoutMs %s",
1545
+ (timeoutMs) => {
1546
+ const { client } = held()
1547
+ const ledger = client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER })
1548
+ expect(() =>
1549
+ client.mutationOptions(ledger, "icrc1_transfer", {
1550
+ timeoutMs: timeoutMs as number,
1551
+ })
1552
+ ).toThrow(/2147483647/)
1553
+ }
1554
+ )
1555
+ })
1556
+
1557
+ /**
1558
+ * Whether the installed TanStack Query hands `onMutate`, `mutationFn` and
1559
+ * `onSettled` the same function context for one run (5.89 and later), through
1560
+ * which `mutationFn` finds the canister `onMutate` resolved, and `onSettled`
1561
+ * the one `mutationFn` wrote to. The peer-floor check runs these tests on an
1562
+ * older release, which passes `mutationFn` and `onSettled` none.
1563
+ */
1564
+ async function sharesRunContext(): Promise<boolean> {
1565
+ const seen: unknown[] = []
1566
+ await new MutationObserver(new QueryClient(), {
1567
+ onMutate: (_vars: void, context?: unknown) => {
1568
+ seen.push(context)
1569
+ },
1570
+ mutationFn: (_vars: void, context?: unknown) => {
1571
+ seen.push(context)
1572
+ return Promise.resolve()
1573
+ },
1574
+ onSettled: (
1575
+ _data: unknown,
1576
+ _error: unknown,
1577
+ _vars: void,
1578
+ _onMutateResult: unknown,
1579
+ context?: unknown
1580
+ ) => {
1581
+ seen.push(context)
1582
+ },
1583
+ }).mutate()
1584
+ return (
1585
+ seen.length === 3 &&
1586
+ seen[0] !== undefined &&
1587
+ seen.every((context) => context === seen[0])
1588
+ )
1589
+ }
1590
+
1591
+ /** A client in a page, on `replica`, with nobody signed in. */
1592
+ function createClientSignedOut(replica: ReturnType<typeof replicaWith>) {
1593
+ return clientWithAuth(replica, createTestAuth({ seed: 3, signedIn: false }))
1594
+ }