@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,2061 @@
1
+ /**
2
+ * `createClient`: the one object an app builds, per browser tab or per server
3
+ * request. It decides who calls (a fixed identity, or the signed-in user of an
4
+ * auth it owns), keeps one immutable agent per principal, and owns the
5
+ * `QueryClient` every read is cached in.
6
+ *
7
+ * Public: {@link createClient}, {@link Client}, {@link ClientOptions},
8
+ * {@link AuthLike} and {@link AuthState}. {@link internalsOf} and the types it
9
+ * returns are for the canister builders of this package (`canister.ts`,
10
+ * `options.ts`, `call.ts`) and are not exported from the package entry.
11
+ *
12
+ * Who calls is decided in one place, {@link ClientInternals.agentFor}, and by
13
+ * one rule: a call goes out signed by the principal it was made for, or not at
14
+ * all. An agent is never re-pointed at another identity (`replaceIdentity` is
15
+ * never called); a sign-in, a sign-out or a switch of account makes the next
16
+ * call use another principal's agent, and a call made for the principal that
17
+ * was current before is cancelled instead of being sent as the new one.
18
+ *
19
+ * @module
20
+ */
21
+ import type { FuncValue, Principal, Schema } from "@meantjs/schema"
22
+ import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_ELEMENTS } from "@meantjs/schema/codec"
23
+ import {
24
+ AnonymousIdentity,
25
+ HttpAgent,
26
+ type Identity,
27
+ } from "@icp-sdk/core/agent"
28
+ import { Principal as SdkPrincipal } from "@icp-sdk/core/principal"
29
+ import { MutationCache, QueryClient, type QueryKey } from "@tanstack/query-core"
30
+ import {
31
+ createReactorError,
32
+ fromCanister,
33
+ notSigned,
34
+ retryDefault,
35
+ } from "./errors.js"
36
+ import { deserializeData, serializeData } from "./hydration.js"
37
+ import { KEY_ROOT } from "./keys.js"
38
+ import {
39
+ agentOptionsFor,
40
+ agentUrlOf,
41
+ authNetworkFor,
42
+ checkKeys,
43
+ resolveCanisterId,
44
+ resolveNetwork,
45
+ type Network,
46
+ type ResolvedNetwork,
47
+ } from "./network.js"
48
+ import { keptUnknown, type CallRequest } from "./call.js"
49
+ import { readTarget, recordOf } from "./canister.js"
50
+ import { createBuilders } from "./options.js"
51
+ import { isDevelopment, isServer } from "./runtime.js"
52
+ import type {
53
+ Canister,
54
+ CanisterMutationOptions,
55
+ CanisterQueryOptions,
56
+ CanisterTarget,
57
+ DataOf,
58
+ ErrorOf,
59
+ MutationOptionsOptions,
60
+ OptInMethod,
61
+ OptInQueryArgs,
62
+ QueryArgs,
63
+ ReadMethod,
64
+ Resend,
65
+ ResendOptions,
66
+ VarsOf,
67
+ } from "./types.js"
68
+
69
+ /** The anonymous principal, `2vxsx-fae`: the caller of every call nobody signed. */
70
+ const ANONYMOUS = SdkPrincipal.anonymous().toText()
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Public types
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /**
77
+ * A sign-in the client can own: what `@icp-sdk/auth` 10's `AuthClient` and the
78
+ * `auth` of `createTestClient()` from `@ic-reactor/core/testing` both are, with
79
+ * no adapter.
80
+ *
81
+ * The client reads `getStatus()` and `getPrincipal()` synchronously to decide
82
+ * who calls, asks `getIdentity()` for the identity to sign with only when it
83
+ * builds or checks an agent for a signed-in principal, and re-reads both after
84
+ * every `subscribe()` notification. `signIn()` and `signOut()` are forwarded
85
+ * from {@link Client.signIn} and {@link Client.signOut}, and `dispose()`, when
86
+ * present, is called from {@link Client.dispose}: the client owns the auth its
87
+ * factory built.
88
+ *
89
+ * Another sign-in source that hands over an `Identity` (a delegation, such
90
+ * as a hand-rolled session gives, or a local key) fits by implementing these
91
+ * six methods, and `dispose()` if it holds anything. A wallet that signs each call itself (ICRC-25 and
92
+ * ICRC-49: OISY, Plug, NFID accounts) gives no `Identity` and does not fit:
93
+ * the Auth guide shows writes through such a wallet beside the client. A
94
+ * source's `getIdentity()` should return the same object until the identity
95
+ * really changes: the client builds a new agent for every new identity object
96
+ * it is handed, and reads a new object for the same principal as a renewal.
97
+ */
98
+ export interface AuthLike {
99
+ /** Who calls are accepted as, or `undefined` when nobody can act. */
100
+ getPrincipal(): { toText(): string } | undefined
101
+ /**
102
+ * Who is signed in. Only `"signed-in"` makes the client call as
103
+ * {@link AuthLike.getPrincipal}; every other state calls anonymously. The
104
+ * four named states are `@icp-sdk/auth` 10's; any other string, such as a
105
+ * state a later `@icp-sdk/auth` adds, is accepted and reads as
106
+ * `"anonymous"` in {@link AuthState}, so an upgrade of the auth library
107
+ * does not stop an app compiling.
108
+ */
109
+ getStatus(): {
110
+ readonly state:
111
+ | "signed-in"
112
+ | "signed-in-elsewhere"
113
+ | "expired"
114
+ | "signed-out"
115
+ | (string & {})
116
+ /**
117
+ * When the session ends, in milliseconds since the epoch (`Date.now()`'s
118
+ * scale), as `AuthClient` 10 reports it. While the status is
119
+ * `"signed-in"` or `"signed-in-elsewhere"`, the client sets a timer for it
120
+ * and tells its listeners when the session ends: an auth whose status
121
+ * turns `"expired"` by the clock, as `AuthClient`'s does, need not notify
122
+ * for it. It is {@link AuthState.expiresAt} while signed in.
123
+ */
124
+ readonly expiresAtMs?: number
125
+ }
126
+ /** The identity to sign calls with while signed in. */
127
+ getIdentity(): Promise<Identity>
128
+ /** Calls `listener` after each change of who is signed in. Returns a function that stops listening. */
129
+ subscribe(listener: () => void): () => void
130
+ signIn(options?: unknown): Promise<unknown>
131
+ signOut(options?: unknown): Promise<unknown>
132
+ /** Releases what the auth holds. Called once, from {@link Client.dispose}. */
133
+ dispose?(): void
134
+ }
135
+
136
+ /**
137
+ * Who a client calls as, and why. {@link Client.authState} returns it.
138
+ *
139
+ * `principal` is always the principal calls go out as, the same text
140
+ * {@link Client.caller} returns: the signed-in user's when `status` is
141
+ * `"signed-in"`, and the anonymous principal (`2vxsx-fae`) in every other
142
+ * state. An expired session, or one signed in on a sibling origin that this
143
+ * origin holds no credential for, cannot sign a call, so it does not name its
144
+ * principal here either: a check such as `principal !== anonymous` means what
145
+ * it looks like it means. To say whose session ended, read the auth's own
146
+ * `getStatus()`, which keeps the principal in those states.
147
+ *
148
+ * A client built with an `identity` reports `"signed-in"` with that
149
+ * identity's principal, or `"anonymous"` for `identity: "anonymous"` and for
150
+ * an explicit `AnonymousIdentity`. A client on a server reports
151
+ * `"anonymous"`.
152
+ */
153
+ export interface AuthState {
154
+ /**
155
+ * - `anonymous`: nobody is signed in (the auth says `signed-out`, or a state
156
+ * the client does not know), the client runs on a server, or it was built
157
+ * anonymous.
158
+ * - `signed-in`: calls are signed by `principal`.
159
+ * - `expired`: the session ended; calls are anonymous until a sign-in.
160
+ * - `signed-in-elsewhere`: someone is signed in on this domain but this
161
+ * origin holds no credential for them yet; calls are anonymous.
162
+ */
163
+ readonly status: "anonymous" | "signed-in" | "expired" | "signed-in-elsewhere"
164
+ /** The principal calls go out as, as text. */
165
+ readonly principal: string
166
+ /**
167
+ * When the session ends, in milliseconds since the epoch (`Date.now()`'s
168
+ * scale; `new Date(expiresAt)` for a date), so an app can show "signed in
169
+ * until". Present only while `status` is `"signed-in"` through an auth whose
170
+ * `getStatus()` carries `expiresAtMs`, as `AuthClient` 10's does; absent for
171
+ * a client built with `identity`. The client tells its listeners when that
172
+ * time passes, and the status turns `"expired"`.
173
+ *
174
+ * A `"signed-in"` session may still be read-only: Internet Identity can
175
+ * scope a session to queries, `AuthClient` reports it `"signed-in"` all the
176
+ * same, and the network refuses its updates. Nothing here tells the two
177
+ * apart yet.
178
+ */
179
+ readonly expiresAt?: number
180
+ }
181
+
182
+ /**
183
+ * Options for {@link createClient}: a network, and exactly one of `identity`
184
+ * and `auth`, so that who calls is always written down.
185
+ */
186
+ export type ClientOptions = {
187
+ /** Where the canisters are. See {@link Network}. */
188
+ readonly network: Network
189
+ /**
190
+ * Whether the `ic_env` cookie (root key and canister ids) is believed. See
191
+ * the `network` option; absent trusts it only when both the page and the
192
+ * replica are local. Even `true` never takes the cookie's root key for a
193
+ * mainnet host, and `true` is refused (a `TypeError`) on a page under
194
+ * `ic0.app`, `github.dev`, `gitpod.io`, `loca.lt` or `devtunnels.ms`, which
195
+ * are not public suffixes.
196
+ */
197
+ readonly allowEnvConfig?: boolean
198
+ /**
199
+ * The `fetch` every agent of this client sends with. Defaults to the global
200
+ * one. Tests pass a fake replica's `fetch` here; nothing global is touched.
201
+ */
202
+ readonly fetch?: typeof globalThis.fetch
203
+ /**
204
+ * How deep a decoded reply may nest. Defaults to 256. Raise it for replies
205
+ * that nest deeper, such as an ICRC-3 block log's values.
206
+ */
207
+ readonly maxDepth?: number
208
+ /**
209
+ * How many values a decoded reply may hold, beyond one per byte of the
210
+ * reply. Defaults to 1,000,000. A reply is always allowed one value per byte
211
+ * on top of this, so a blob (a wasm module, an asset chunk) of any size the
212
+ * IC returns decodes; this bounds only the values that take no bytes, such
213
+ * as a long `vec null`.
214
+ */
215
+ readonly maxElements?: number
216
+ } & (
217
+ | {
218
+ /**
219
+ * A fixed caller. `"anonymous"` makes a read-only client: every update
220
+ * is refused before it is sent. An `Identity` signs every call, updates
221
+ * included; pass an explicit `new AnonymousIdentity()` for an anonymous
222
+ * client that may send updates.
223
+ */
224
+ readonly identity: Identity | "anonymous"
225
+ readonly auth?: never
226
+ }
227
+ | {
228
+ /**
229
+ * Builds the sign-in this client calls as, such as
230
+ * `(network) => new AuthClient(network)`. It is called once, on first
231
+ * use, and only in a browser: on a server the client is anonymous and
232
+ * never calls it. The client owns what it returns and disposes it with
233
+ * itself.
234
+ *
235
+ * A factory that throws (`new AuthClient()` reads `localStorage`, which
236
+ * throws where site data is blocked) is not called again: the client
237
+ * calls anonymously, so public reads still work, logs the error once in
238
+ * development, and rejects {@link Client.signIn} and
239
+ * {@link Client.signOut} with what the factory threw. The same holds
240
+ * for a factory that returns something other than an {@link AuthLike},
241
+ * such as a Promise (an `async` factory, or `AuthClient.create()` of
242
+ * `@icp-sdk/auth` 5 and earlier): the error is then a `TypeError`
243
+ * naming what the result lacks.
244
+ *
245
+ * `network` is the client's network under `@icp-sdk/auth` 10's own
246
+ * option names, so `new AuthClient(network)` signs in where the client
247
+ * calls:
248
+ *
249
+ * - `agentOptions`: the host, root key and `shouldFetchRootKey` of the
250
+ * client's own agents, and its `fetch`, when you gave one.
251
+ * - `identityProvider`: the Internet Identity to sign in with. The
252
+ * `INTERNET_IDENTITY_PROVIDER` of an `ic_env` cookie believed in full,
253
+ * on a local page or with `allowEnvConfig: true` (with its
254
+ * `PUBLIC_CANISTER_ID:internet_identity`, or
255
+ * `rdmx6-jaaaa-aaaaa-aaadq-cai`); never that of a cookie believed for
256
+ * canister ids only, as on `<id>.icp0.io`; else, for `"local"` or a local host,
257
+ * icp-cli's built-in one at `http://id.ai.localhost:<port>/authorize`;
258
+ * else absent, which `AuthClient` reads as mainnet's.
259
+ *
260
+ * Absent on a network that is not mainnet's (a project that deploys its
261
+ * own `internet_identity`, `"env"` on a local page with no provider in
262
+ * the cookie, a replica with its own root key), sign-in cannot work: a
263
+ * replica rejects the delegations mainnet's Internet Identity mints.
264
+ * The client says so once, in development, when the factory reads
265
+ * `network.identityProvider`, as `new AuthClient(network)` does. Name
266
+ * the provider:
267
+ * `(network) => new AuthClient({ ...network, identityProvider: { authorizeUrl, canisterId } })`.
268
+ * A factory that names its own provider, as that one does, or builds
269
+ * an auth that is not Internet Identity, never sees the warning. Nor
270
+ * does a spread that adds other options and names no provider, such as
271
+ * `{ ...network, derivationOrigin }`: it must name one on such a network.
272
+ *
273
+ * A factory that takes no argument, such as `() => new AuthClient()`,
274
+ * still works on mainnet. It ignores `network`, so on any other network
275
+ * it signs in with mainnet's Internet Identity: pass `network` through.
276
+ */
277
+ readonly auth: (network: {
278
+ readonly agentOptions: {
279
+ readonly host: string
280
+ readonly rootKey?: Uint8Array
281
+ readonly shouldFetchRootKey: boolean
282
+ readonly fetch?: typeof globalThis.fetch
283
+ }
284
+ readonly identityProvider?: {
285
+ readonly authorizeUrl: string
286
+ readonly canisterId: string
287
+ }
288
+ }) => AuthLike
289
+ readonly identity?: never
290
+ }
291
+ )
292
+
293
+ /** What the `auth` factory is handed: `network` in `(network) => new AuthClient(network)`. */
294
+ type AuthNetworkArgument = Parameters<
295
+ Extract<ClientOptions, { readonly auth: unknown }>["auth"]
296
+ >[0]
297
+
298
+ /**
299
+ * A client made by {@link createClient}: one per browser tab, or one per
300
+ * request on a server, so that no cache or agent is shared between users.
301
+ */
302
+ export interface Client {
303
+ /** The network's segment in query keys: `"ic"`, `"local"`, a custom network's `name ?? host`, or the resolved host for `"env"`. */
304
+ readonly network: string
305
+ /**
306
+ * The `QueryClient` this client owns. Its default `retry` retries a
307
+ * `ReactorError` only after a failure that proves it was not delivered, and
308
+ * any other error (an app's own REST query on this `QueryClient`) as
309
+ * TanStack Query does by default: up to 3 times; neither is retried on a
310
+ * server. Its dehydrate and hydrate defaults keep `bigint`, `Uint8Array`
311
+ * and the floats JSON cannot write, so a server's `dehydrate()` survives
312
+ * `JSON.stringify` and comes back exact.
313
+ */
314
+ readonly queryClient: QueryClient
315
+ /** The principal calls go out as now, as text: the anonymous principal unless signed in. */
316
+ caller(): string
317
+ /**
318
+ * Who calls go out as, and why. The object is the same until the status,
319
+ * the principal or `expiresAt` changes, so it can be read with
320
+ * `useSyncExternalStore`.
321
+ */
322
+ authState(): AuthState
323
+ /**
324
+ * Calls `listener` once after each change of {@link Client.authState}, and
325
+ * never for a notification of the auth that changed none of the status,
326
+ * the principal and `expiresAt` (a renewed delegation, for one). A session
327
+ * that ends by the clock is a change too: the client sets a timer for its
328
+ * {@link AuthState.expiresAt}, so listeners hear of it without any
329
+ * notification of the auth, and a read of {@link Client.caller} or
330
+ * {@link Client.authState} that sees a change first tells them in a
331
+ * microtask. Returns a function that stops listening. On a disposed client
332
+ * it adds nothing.
333
+ */
334
+ subscribe(listener: () => void): () => void
335
+ /**
336
+ * Signs in through the client's auth, passing `options` on. Listeners are
337
+ * told once the auth settles, even if the auth itself did not notify.
338
+ * Rejects a `TypeError` on a client built with `identity`, which has no
339
+ * sign-in, an `Error` on a server or after {@link Client.dispose}, and
340
+ * with what the auth factory threw when it threw (a `TypeError` when it
341
+ * returned something other than an {@link AuthLike}).
342
+ *
343
+ * A call in the same synchronous run as an earlier one with the same
344
+ * options (the same value, or an object with the same own enumerable keys
345
+ * and values) returns that call's promise and does not ask the auth again.
346
+ * React's `StrictMode` runs an effect twice in one run, and an Internet
347
+ * Identity sign-in over `transport: "redirect"` that starts twice fails on
348
+ * the load it comes back to. A call in a later run, such as a second click,
349
+ * signs in again.
350
+ */
351
+ signIn(options?: unknown): Promise<void>
352
+ /**
353
+ * Signs out through the client's auth, passing `options` on (such as
354
+ * `AuthClient`'s `{ returnTo }`). Rejects like {@link Client.signIn}.
355
+ */
356
+ signOut(options?: unknown): Promise<void>
357
+ /**
358
+ * Releases everything the client holds: it stops listening to its auth and
359
+ * disposes it, clears the `QueryClient`, and drops its agents. Calling it
360
+ * again does nothing.
361
+ *
362
+ * Every call made on the client afterwards, a write included, rejects
363
+ * `cancelled` with code `client_disposed` and `mayHaveExecuted: false`, and
364
+ * sends nothing: a direct call, a query or mutation function, a func
365
+ * reference's function. A call still waiting to be sent (for its caller's
366
+ * identity, or to be sent again) is cancelled the same way. A direct call
367
+ * already sent settles as the replica answers; a read the client's
368
+ * `QueryClient` was running is dropped with the cache.
369
+ */
370
+ dispose(): void
371
+
372
+ /**
373
+ * A canister to call: a frozen object with one plain async method per
374
+ * Candid method of `service`, typed from the generated `Actor` you name.
375
+ *
376
+ * ```ts
377
+ * import { actor, type Actor } from "./canisters/icrc1"
378
+ * const ledger = client.canister<Actor>(actor, { id: LEDGER_ID })
379
+ * const balance = await ledger.icrc1_balance_of({ owner, subaccount: null })
380
+ * ```
381
+ *
382
+ * `service` is the `actor` export of a module `meant gen` wrote,
383
+ * or the `actor` of `schemaFromContract()`: both make the same calls and the
384
+ * same keys. `<A>` is not checked against `service`; pass the `Actor` of
385
+ * the same module.
386
+ *
387
+ * A method sends its call as the caller signed in at the moment it is
388
+ * called, decodes the reply, and resolves with it as the `Actor` types it,
389
+ * except that a method whose one result is an `Ok`/`Err` variant resolves
390
+ * with the `Ok` payload and rejects `canister_err` with the `Err` one. An
391
+ * update or a oneway by a caller who is not signed in rejects
392
+ * `unauthenticated` and sends nothing. Every failure is a `ReactorError`;
393
+ * see {@link Canister} for what is re-sent.
394
+ *
395
+ * The same service object and the same target give the same canister
396
+ * object on every call, so it can be made during render. See
397
+ * {@link CanisterTarget} for `{ id }`, `{ name }` and `certified`.
398
+ *
399
+ * @throws TypeError for a `service` that is not a service schema, or a
400
+ * target that is not one of the allowed shapes or whose `id` is not
401
+ * principal text.
402
+ */
403
+ canister<A>(service: Schema<Principal>, target: CanisterTarget): Canister<A>
404
+
405
+ /**
406
+ * The canister id a target resolves to now, by the same resolver the calls
407
+ * of `client.canister(service, target)` use, without making a call.
408
+ *
409
+ * ```ts
410
+ * const backend = client.canisterId({ name: "backend" })
411
+ * if (!backend.ok && backend.reason === "untrusted_host") showSetupHint()
412
+ * ```
413
+ *
414
+ * `{ id }` gives that id. `{ name }` is read from the `ic_env` cookie, as a
415
+ * call reads it, and gives the id or one of three reasons, each with its
416
+ * own fix:
417
+ *
418
+ * - `no_browser`: there is no page, so no cookie to read (a server render,
419
+ * a web worker).
420
+ * - `untrusted_host`: the cookie is not trusted for this network, so it was
421
+ * not read; or a page that is not local holds more than one `ic_env`
422
+ * cookie and they differ, so none is believed.
423
+ * - `absent`: the cookie was read and has no principal text under that
424
+ * name.
425
+ *
426
+ * `message` is the resolver's reason, the text a call on that target puts
427
+ * in its error after the method and target. The answer is not kept: each call reads the cookie again, as each call of a
428
+ * `{ name }` canister does. It sends nothing, and a `certified` flag in the
429
+ * target is ignored. An id it gives is the one the cookie names; whether a
430
+ * canister answers there is not checked.
431
+ *
432
+ * @throws TypeError for a target that is not one of the allowed shapes or
433
+ * whose `id` is not principal text.
434
+ */
435
+ canisterId(target: CanisterTarget):
436
+ | { readonly ok: true; readonly id: string }
437
+ | {
438
+ readonly ok: false
439
+ readonly reason: "no_browser" | "untrusted_host" | "absent"
440
+ readonly message: string
441
+ }
442
+
443
+ /**
444
+ * The query key of a read, or the prefix of a group of reads, for
445
+ * `queryClient.invalidateQueries`, `getQueryData` and filters:
446
+ *
447
+ * - `queryKey(c)`: every read of the canister by the current caller.
448
+ * - `queryKey(c, method)`: every read of that method by the current caller,
449
+ * whatever its arguments. For a method without arguments, which has one
450
+ * read, this is that read's key, the same as `queryKey(c, method,
451
+ * undefined)`, so `queryClient.getQueryData(client.queryKey(ledger,
452
+ * "icrc1_fee"))` finds what `queryOptions(ledger, "icrc1_fee")` cached.
453
+ * - `queryKey(c, method, vars)`: the key `queryOptions(c, method, vars)`
454
+ * gives, as built now.
455
+ *
456
+ * `getQueryData` and `setQueryData` match a key exactly: hand them a read's
457
+ * whole key, never a prefix (a method's with arguments, or a canister's).
458
+ *
459
+ * Keys are `['ic-reactor', network, caller, canisterId, method, args]` plus
460
+ * `'certified'` for a certified canister (DECISIONS Q5). Build them with
461
+ * this method, never by hand: a key typed out as an array literal is a
462
+ * `QueryKey` like any other, and matches nothing the client invalidates.
463
+ * It never throws for `vars` that do not encode: the key then holds
464
+ * `'$invalid'` and a text of the value.
465
+ *
466
+ * @throws TypeError for a canister of another client, or a method the
467
+ * service does not have.
468
+ */
469
+ queryKey<A, M extends keyof A & string>(
470
+ canister: Canister<A>,
471
+ method?: M,
472
+ vars?: VarsOf<A, M>
473
+ ): QueryKey
474
+
475
+ /**
476
+ * TanStack Query options for a read: `useQuery(client.queryOptions(ledger,
477
+ * "icrc1_balance_of", account))`, `queryClient.fetchQuery(...)`, a
478
+ * `QueryObserver`.
479
+ *
480
+ * The variables after `method` follow one rule (DECISIONS Q3): nothing for
481
+ * a method without arguments, the value for one argument, the tuple for two
482
+ * or more. Pass `skipToken` (from `@tanstack/query-core` or
483
+ * `@tanstack/react-query`) in their place while they are not known yet.
484
+ *
485
+ * The read is made as the caller current when the options are built: the
486
+ * key holds their principal, and the query function refuses to run
487
+ * (`cancelled`, nothing sent) once someone else is signed in. Build the
488
+ * options again when the caller changes, as a component does on every
489
+ * render, and the read moves to the new caller's key: one principal's data
490
+ * is never shown under another's.
491
+ *
492
+ * Arguments that do not encode give a key tagged `'$invalid'` and a query
493
+ * function that rejects `invalid_args` with the codec's issues, so a render
494
+ * never throws on half-typed input.
495
+ *
496
+ * `retry` retries only a failure that proves the call was not delivered, at
497
+ * most 3 times, and never on a server.
498
+ *
499
+ * An update or a oneway method of a generated `Actor` does not compile
500
+ * here (`method` is a `ReadMethod<A>`): a query refetches, and every
501
+ * refetch would run the update again. Use {@link Client.mutationOptions}
502
+ * for a write. The generated `Actor` says each method's mode; a method of
503
+ * an `Actor` written by hand says none and compiles, and this checks it at
504
+ * run time instead, throwing a `TypeError` for an update or a oneway. An
505
+ * update that returns the same answer however often it runs (ckBTC's
506
+ * `get_btc_address`) can be read with `{ update: "idempotent" }` as the
507
+ * fourth argument (the third signature; a oneway does not compile even
508
+ * then): it is fetched once per key and caller (`staleTime: Infinity`, no
509
+ * refetch on mount, focus or reconnect), needs a signed-in caller like any
510
+ * update, and is retried only after a failure that proves it never got in
511
+ * (DECISIONS Q2). It is still a read of its canister, so a write to that
512
+ * canister through {@link Client.mutationOptions} invalidates it by default
513
+ * and it runs once more, as a replicated call: safe for an idempotent
514
+ * method, but a cost. To keep it cached across writes, name the reads a
515
+ * write changes in `invalidates`.
516
+ *
517
+ * A method TypeScript cannot tell is a `ReadMethod<A>` does not compile,
518
+ * even when every value of it names a query: a type parameter
519
+ * (`M extends keyof A & string`), a union that may name an update
520
+ * (`keyof Actor & string`), or any method of a `Canister<A>` whose `A` is
521
+ * itself generic. Give the type arguments,
522
+ * `queryOptions<A, any>(canister, method, vars)`, which keeps the run-time
523
+ * check and types the data `unknown`.
524
+ *
525
+ * A method without results (`() -> () query`) has nothing to cache: its
526
+ * call resolves `undefined`, which TanStack Query takes for a failed read.
527
+ * This throws a `TypeError` for it too; call it directly, as
528
+ * `await canister.method()`.
529
+ *
530
+ * Built from variables that cannot be `skipToken`, the options' `queryFn`
531
+ * is never `skipToken` either, so `useSuspenseQuery` and
532
+ * `useSuspenseQueries` take them as they are. Variables of a type
533
+ * `skipToken` is assignable to, such as `unknown` (a Candid `reserved`
534
+ * argument) or `{}`, may be, so every read of such a method takes the next
535
+ * signature and keeps `SkipToken`.
536
+ *
537
+ * @throws TypeError for an update or oneway method without the opt-in, a
538
+ * method without results, a composite query of a certified canister, a
539
+ * canister of another client, a method the service does not have, or a
540
+ * fourth argument other than `{ update: "idempotent" }`. An update or a
541
+ * oneway reaches this check only through an `Actor` that does not say the
542
+ * method's mode.
543
+ */
544
+ queryOptions<A, M extends ReadMethod<A>>(
545
+ canister: Canister<A>,
546
+ method: M,
547
+ ...args: QueryArgs<A, M, never>
548
+ ): CanisterQueryOptions<DataOf<A, M>, ErrorOf<A, M>, never>
549
+ /**
550
+ * TanStack Query options for a read whose variables may be `skipToken`
551
+ * (`V | SkipToken`, or `skipToken` itself): the options' `queryFn` is
552
+ * `skipToken` for a skipped read, which `useQuery` takes and
553
+ * `useSuspenseQuery` does not. Otherwise the same as the call with
554
+ * variables that cannot be skipped: see that signature for the rules.
555
+ *
556
+ * @throws TypeError as the call with variables that cannot be skipped.
557
+ */
558
+ queryOptions<A, M extends ReadMethod<A>>(
559
+ canister: Canister<A>,
560
+ method: M,
561
+ ...args: QueryArgs<A, M>
562
+ ): CanisterQueryOptions<DataOf<A, M>, ErrorOf<A, M>>
563
+ /**
564
+ * TanStack Query options for an update read like a query, with
565
+ * `{ update: "idempotent" }`: only for an update that returns the same
566
+ * answer however often it runs, such as ckBTC's `get_btc_address`.
567
+ *
568
+ * ```ts
569
+ * useQuery(client.queryOptions(minter, "get_btc_address", arg, { update: "idempotent" }))
570
+ * ```
571
+ *
572
+ * The variables come first, even `undefined` for a method without
573
+ * arguments. A oneway method does not compile here either (`method` is an
574
+ * `OptInMethod<A>`): it has no reply to cache. The read is fetched once per
575
+ * key and caller; see the first signature for the rules every read
576
+ * follows.
577
+ *
578
+ * @throws TypeError as the call without the opt-in, except for an update.
579
+ */
580
+ queryOptions<A, M extends OptInMethod<A>>(
581
+ canister: Canister<A>,
582
+ method: M,
583
+ ...args: OptInQueryArgs<A, M, never>
584
+ ): CanisterQueryOptions<DataOf<A, M>, ErrorOf<A, M>, never>
585
+ /**
586
+ * The opt-in read of an update, with variables that may be `skipToken`:
587
+ * its `queryFn` is `skipToken` for a skipped read. Otherwise the same as
588
+ * the opt-in call with variables that cannot be skipped.
589
+ *
590
+ * @throws TypeError as the call without the opt-in, except for an update.
591
+ */
592
+ queryOptions<A, M extends OptInMethod<A>>(
593
+ canister: Canister<A>,
594
+ method: M,
595
+ ...args: OptInQueryArgs<A, M>
596
+ ): CanisterQueryOptions<DataOf<A, M>, ErrorOf<A, M>>
597
+
598
+ /**
599
+ * TanStack Query options for a write: `useMutation(client.mutationOptions(
600
+ * ledger, "icrc1_transfer"))`, then `mutate(arg)` with the same argument
601
+ * convention as {@link Client.queryOptions}.
602
+ *
603
+ * The mutation function calls the method as the caller current when
604
+ * `mutate()` was called (read in `onMutate`), or not at all: a write that
605
+ * TanStack Query holds back (offline, or behind another mutation of its
606
+ * `scope`) while the account changes rejects `cancelled`, code
607
+ * `caller_changed`, `mayHaveExecuted: false`, and sends nothing; so does a
608
+ * mutation restored from a persister, whose caller is not known. The
609
+ * client's own `queryClient` reads each run's caller in its
610
+ * `MutationCache`, so this holds with an `onMutate` of the app's that
611
+ * replaces this one; the JSDoc of the returned `mutationFn` covers a run on
612
+ * another `QueryClient`. The options of a view (what `useClient()` returns)
613
+ * also call only as the view's principal, and end their `mutationKey` with
614
+ * it: while someone else is current their mutation function rejects
615
+ * `cancelled` the same way. An update by a caller who is not signed in
616
+ * rejects `unauthenticated` before anything is sent. `retry` is `false`: an
617
+ * update
618
+ * re-sends itself only when the failure proves the first attempt never got
619
+ * in (see {@link Canister}), and a TanStack retry would re-send after
620
+ * failures that do not prove it, running the write twice.
621
+ *
622
+ * `onSettled` invalidates the reads the write may have changed, on the
623
+ * `QueryClient` the mutation ran on (which TanStack Query 5.89 and later
624
+ * hands it; an app's own `QueryClientProvider` nested inside the client's)
625
+ * and on this client's `queryClient`, for every caller, certified or not,
626
+ * and waits for the active ones to refetch: after a success, a
627
+ * `canister_err`, and any failure whose `mayHaveExecuted` is `true` (a lost
628
+ * reply: the re-read shows whether it happened). Not after a failure that proves nothing ran
629
+ * (`invalid_args`, `unauthenticated`, `not_delivered`, a reject 1 or 3, a
630
+ * `cancelled` before sending). The reads are every read of the canister
631
+ * written to (DECISIONS Q10), or those listed in `invalidates`: canisters
632
+ * and `[canister, method]` pairs of this client; `[]` invalidates nothing.
633
+ * "Every read" includes an update read with `{ update: "idempotent" }`
634
+ * (such as a ckBTC minter's `get_btc_address` after `update_balance`),
635
+ * whose re-read is one more replicated call; list the reads in
636
+ * `invalidates` to leave it cached.
637
+ *
638
+ * A `{ name }` (the canister written to, or one in `invalidates`) is
639
+ * resolved once per run, in `onMutate`, which TanStack Query runs first
640
+ * and whose result it hands to `onSettled`: a write whose `ic_env` cookie
641
+ * entry changes before it settles (a local redeploy) invalidates the reads
642
+ * of the canister it wrote to. To add your own `onSettled`, call this one
643
+ * from it with every argument it gets; to add your own `onMutate`, call
644
+ * this one from it with both and spread its result into yours:
645
+ * `{ ...options.onMutate(variables, context), previous }`. An `onMutate`
646
+ * that replaces this one keeps the write and its invalidation together
647
+ * from TanStack Query 5.89 on, which hands `mutationFn` and `onSettled` the
648
+ * same run context: `mutationFn` resolves the targets when it starts, and
649
+ * `onSettled` reads them from there. Before 5.89 it leaves `mutationFn` and
650
+ * `onSettled` each to resolve the targets when it runs, so a cookie
651
+ * rewritten between the two can split the write from its invalidation.
652
+ *
653
+ * @throws TypeError for a canister of another client, a method a service
654
+ * does not have, or a third argument other than `{ invalidates }`.
655
+ */
656
+ mutationOptions<A, M extends keyof A & string>(
657
+ canister: Canister<A>,
658
+ method: M,
659
+ options?: MutationOptionsOptions
660
+ ): CanisterMutationOptions<VarsOf<A, M>, DataOf<A, M>, ErrorOf<A, M>>
661
+
662
+ /**
663
+ * The write `error` reports, offered to send again exactly as it was sent,
664
+ * by whoever sent it, when its outcome is unknown; `undefined` when there
665
+ * is nothing to offer.
666
+ *
667
+ * Only for a method that deduplicates: one that runs the same argument
668
+ * from the same sender at most once, as an ICRC-1 ledger does for a
669
+ * transfer with `created_at_time` set (it answers `Duplicate` the second
670
+ * time). Sending again a write that does not deduplicate can run it twice:
671
+ * there, read the state back instead. `dedupedBy` states what the canister
672
+ * deduplicates on, read from the argument:
673
+ *
674
+ * ```ts
675
+ * const transfer = useMutation(client.mutationOptions(ledger, "icrc1_transfer"))
676
+ * const again = client.resendOf(transfer.error, ledger, "icrc1_transfer", {
677
+ * dedupedBy: (arg) => arg.created_at_time,
678
+ * })
679
+ * // again && <button onClick={() => transfer.mutate(again.arg)}>Send the same transfer again</button>
680
+ * ```
681
+ *
682
+ * It offers a re-send only when all of these hold, and is read again on
683
+ * every call, so read it during render, in a component that re-renders
684
+ * when the caller changes (one that calls `useAuth()`):
685
+ *
686
+ * - `error` is one a write of `method` on `canister` rejected with (a
687
+ * direct call, or a mutation function built from the canister), with
688
+ * `mayHaveExecuted: true`. Any other failure proves the outcome, and a
689
+ * success needs no re-send.
690
+ * - Its argument is an object: a record or a variant, or the list of
691
+ * several arguments.
692
+ * - `dedupedBy(arg)` is neither `undefined` nor `null`.
693
+ * - The argument still encodes to the bytes the attempt sent: an argument
694
+ * object changed since then is not offered.
695
+ * - `canister` still resolves to the canister id the attempt went to: a
696
+ * `{ name }` the `ic_env` cookie maps elsewhere since is not offered.
697
+ * - The principal that sent it is the caller now. After a sign-out, or a
698
+ * sign-in as another principal, nothing is offered: the same argument
699
+ * from another sender is a new write. Signing in again as the sender
700
+ * offers it again. A view of the client pinned to a principal (what
701
+ * `useClient()` returns) answers for the live caller, whom a write goes
702
+ * out as.
703
+ *
704
+ * The offer goes out only as the same write. `send()` sends the bytes the
705
+ * attempt sent, to the canister id it went to. `mutate(arg)`, on the
706
+ * mutation that failed (which keeps its pending state and invalidation) or
707
+ * any other of this client, sends `arg` only to the method and canister id
708
+ * the attempt went to, and only while it encodes to the same bytes. Either
709
+ * one goes out only as `from`. Each refusal sends nothing, and the code
710
+ * says which it is: `caller_changed` once someone else is the caller,
711
+ * `target_changed` for another method or canister id, and `arg_changed`
712
+ * for an argument changed since it was sent. A re-send that is refused, or
713
+ * turned away when sent (an HTTP 4xx, reject code 1 to 3), still rejects
714
+ * `outcome_unknown` with `mayHaveExecuted: true`, because the attempt it
715
+ * re-sends may have run, and this method offers that write again for that
716
+ * error, under the same conditions as for the first. Once
717
+ * offered, that object stays held to that write: build a new argument for
718
+ * a new write, rather than change this one or send it from another account.
719
+ *
720
+ * @throws TypeError for a canister of another client, a method the service
721
+ * does not have, or no `dedupedBy` function.
722
+ */
723
+ resendOf<A, M extends keyof A & string>(
724
+ error: unknown,
725
+ canister: Canister<A>,
726
+ method: M,
727
+ options: ResendOptions<VarsOf<A, M>>
728
+ ): Resend<VarsOf<A, M>, DataOf<A, M>> | undefined
729
+
730
+ /**
731
+ * The Candid bytes a call of `method` with `vars` sends, as a `Uint8Array`:
732
+ * made by the call path's own encoder with its own variables rule (the
733
+ * value for one argument, the tuple for several, nothing for none), so
734
+ * they are the bytes that go out, not a second encoding. Pass them where
735
+ * the exact argument is wanted, such as an ICRC-21 consent request's `arg`:
736
+ *
737
+ * ```ts
738
+ * const arg = client.encode(ledger, "icrc1_transfer", transfer)
739
+ * const consent = await ledger.icrc21_canister_call_consent_message({
740
+ * method: "icrc1_transfer",
741
+ * arg,
742
+ * user_preferences: preferences,
743
+ * })
744
+ * ```
745
+ *
746
+ * It sends nothing and needs no canister id, so a `{ name }` the `ic_env`
747
+ * cookie does not resolve encodes too.
748
+ *
749
+ * It checks only the variables: not the caller, the canister id or
750
+ * whether the client is disposed, which a call checks before it encodes.
751
+ *
752
+ * @throws ReactorError `invalid_args` for variables that do not encode,
753
+ * with the `issues` and `reason` a call rejects with when its variables are
754
+ * all that is wrong.
755
+ * @throws TypeError for a canister of another client, or a method the
756
+ * service does not have.
757
+ */
758
+ encode<A, M extends keyof A & string>(
759
+ canister: Canister<A>,
760
+ method: M,
761
+ vars: VarsOf<A, M>
762
+ ): Uint8Array
763
+
764
+ /**
765
+ * An async function for a func reference a reply carried, such as an ICRC
766
+ * ledger's archive callback:
767
+ *
768
+ * ```ts
769
+ * import { QueryArchiveFn, type GetBlocksArgs, type BlockRange } from "./canisters/ledger"
770
+ * for (const range of reply.archived_blocks) {
771
+ * const read = client.func<(arg: GetBlocksArgs) => Promise<BlockRange>>(QueryArchiveFn, range.callback)
772
+ * const { blocks } = await read({ start: range.start, length: range.length })
773
+ * }
774
+ * ```
775
+ *
776
+ * The call goes to `ref.principal` and `ref.method`, with the arguments,
777
+ * results and mode of `funcSchema` (the generated schema of the func type),
778
+ * through the same path as a canister's methods: the same errors, the same
779
+ * caller rules, the same unwrapping. `F` is not checked against
780
+ * `funcSchema`: a generated func type carries no signature, so write the
781
+ * one its `.did` gives, with the `Ok` payload as the reply of a result.
782
+ *
783
+ * @throws TypeError for a schema that is not a func schema, or a `ref`
784
+ * whose principal is not principal text or whose method is not a name.
785
+ */
786
+ func<F extends (...args: never[]) => Promise<unknown>>(
787
+ funcSchema: Schema<FuncValue>,
788
+ ref: FuncValue
789
+ ): F
790
+
791
+ /**
792
+ * The IC's time, in nanoseconds since the epoch (the unit of an ICRC-1
793
+ * `created_at_time`), from a certified `read_state` of the `time` path,
794
+ * routed by `canister`'s subnet. Stamp a deduplication key with it rather
795
+ * than with the host's clock, which a ledger checks against its own.
796
+ *
797
+ * ```ts
798
+ * const created_at_time = await client.time(ledger)
799
+ * ```
800
+ *
801
+ * The IC's time moves about once a second (once per round), so two writes
802
+ * stamped close together can carry the same time: give each its own
803
+ * `memo` as well.
804
+ *
805
+ * Its request carries an ingress expiry from the host's clock, as every
806
+ * request does: a host clock so far off that the IC refuses its requests
807
+ * gets `not_delivered` here too.
808
+ *
809
+ * The certificate is verified against the network's root key, as every
810
+ * reply is; its age is not compared with the host's clock, which may be the
811
+ * clock that is wrong. A certificate that does not verify rejects
812
+ * `not_delivered` with `mayHaveExecuted: false`, and its time is never
813
+ * returned. It needs no signed-in caller, and is sent by the caller's
814
+ * agent, as a read does. A `{ name }` the `ic_env` cookie
815
+ * does not resolve rejects `invalid_args` (`canister_id_unresolved`), as
816
+ * does the management canister `aaaaa-aa` (`management_canister`), which no
817
+ * `read_state` is routed by, and a disposed client rejects `cancelled`
818
+ * (`client_disposed`); nothing is sent then.
819
+ *
820
+ * A canister object made with `timeoutMs` bounds the wait, counted from the
821
+ * moment `time()` is called, as it bounds a call through it. When the bound
822
+ * passes, `time()` rejects `cancelled` with code `"timeout"` and
823
+ * `mayHaveExecuted: false`; a request already out is not torn down, only
824
+ * the waiting stops, and one not sent yet is never sent. Without
825
+ * `timeoutMs`, a `read_state` that gets no HTTP answer waits as long as the
826
+ * `fetch` in use does.
827
+ */
828
+ time(canister: Canister<object>): Promise<bigint>
829
+ }
830
+
831
+ // ---------------------------------------------------------------------------
832
+ // Internals for the canister builders
833
+ // ---------------------------------------------------------------------------
834
+
835
+ /** Who calls now. */
836
+ export interface Caller {
837
+ /** The principal calls go out as, as text. */
838
+ readonly principal: string
839
+ /**
840
+ * Whether an update may be sent: signed in with a principal that is not the
841
+ * anonymous one, or built with an `Identity` object (an explicit
842
+ * `AnonymousIdentity` included, the deliberate way to write anonymously).
843
+ * `identity: "anonymous"` never is.
844
+ */
845
+ readonly authenticated: boolean
846
+ }
847
+
848
+ /** The call an agent is asked for, named in the error that cancels it. */
849
+ export interface AgentContext {
850
+ readonly method: string
851
+ readonly canisterId: string
852
+ }
853
+
854
+ /** What the canister builders read from a client. Not part of the package entry. */
855
+ export interface ClientInternals {
856
+ /** The network, with every decision made. */
857
+ readonly network: ResolvedNetwork
858
+ /** The decode depth bound for replies, from {@link ClientOptions.maxDepth}. */
859
+ readonly maxDepth: number
860
+ /** The decode element budget for replies, from {@link ClientOptions.maxElements}. */
861
+ readonly maxElements: number
862
+ /** Who calls now. Reads the auth (building it on first use, in a browser). */
863
+ current(): Caller
864
+ /**
865
+ * Whether {@link Client.dispose} has run. The call path asks it before
866
+ * anything else, so that every call made on a disposed client is cancelled
867
+ * (`client_disposed`): a disposed client calls as the anonymous principal,
868
+ * and a write would otherwise be refused as `unauthenticated`.
869
+ */
870
+ disposed(): boolean
871
+ /**
872
+ * The caller current when each mutation run of the client's `queryClient`
873
+ * started, by its `Mutation` and (TanStack Query 5.89 and later) by the
874
+ * function context TanStack made for the run. Read by the `MutationCache`'s
875
+ * own `onMutate`, which `mutate()` runs before the mutation's `onMutate`
876
+ * and which a mutation's options cannot replace. Weak, so an entry lives as
877
+ * long as its mutation. A restored mutation (hydrated from a persister) has
878
+ * none: TanStack runs no `onMutate` for it.
879
+ */
880
+ readonly mutationCallers: WeakMap<object, Caller>
881
+ /**
882
+ * The `mutationFn` of every `mutationOptions` of this client: the only
883
+ * mutations whose caller the `MutationCache` reads.
884
+ */
885
+ readonly ownMutationFns: WeakSet<object>
886
+ /**
887
+ * The argument of each re-send `resendOf` offered, and the write it was
888
+ * first sent in: a mutation given that very argument sends it as that
889
+ * write's caller, to its canister id, with its bytes, or not at all.
890
+ */
891
+ readonly offered: WeakMap<object, CallRequest>
892
+ /**
893
+ * The agent that signs as `principal`, and only as `principal`. It is the
894
+ * one place the client enforces "a call goes out as the principal it was
895
+ * made for, or not at all".
896
+ *
897
+ * A read's query function passes the principal captured with its query key,
898
+ * so data cached under a principal's key is only ever fetched as that
899
+ * principal. A direct call and a mutation function pass
900
+ * `current().principal` read at call time.
901
+ *
902
+ * It resolves the identity for `principal` (the fixed identity, the
903
+ * client's own `AnonymousIdentity`, or the auth's `getIdentity()` for a
904
+ * signed-in principal), and then checks again: if by then the current
905
+ * principal is another one, the client was disposed, or the identity is not
906
+ * `principal`'s, it rejects a `cancelled` `ReactorError` (code
907
+ * `"caller_changed"` or `"client_disposed"`, `mayHaveExecuted: false`)
908
+ * and nothing is sent. An auth whose `getIdentity()` rejects gives an
909
+ * `unauthenticated` one (code `"identity_unavailable"`).
910
+ *
911
+ * A principal's agent is built once and reused; a new identity object for
912
+ * the same principal (a renewed delegation) builds a new agent and replaces
913
+ * the old one, which is never handed out again. Every agent is built with
914
+ * `retryTimes: 0`, so it never re-sends a request on its own: the call path
915
+ * decides every re-send from the classified failure.
916
+ *
917
+ * `retryTimes` also governs the agent's other requests, and setting it to 0
918
+ * costs two recoveries the call path (`call.ts`) has to make up for
919
+ * itself, as read in `@icp-sdk/core` 6.1:
920
+ *
921
+ * - Update polling. An update the replica answers 202, or with a v4 reply
922
+ * whose certificate has no status for the request, falls back to polling
923
+ * `read_state`. One fetch failure or 5xx during that polling now ends the
924
+ * call, and it classifies as `outcome_unknown` with
925
+ * `mayHaveExecuted: true`: safe, but it gives up on a call the IC may
926
+ * still answer. `read_state` executes nothing, so the call path can send
927
+ * the update with `agent.call` and poll its request id with
928
+ * `pollForResponse`, polling the same request id again after a transient
929
+ * polling failure. That never runs the update twice, and keeps
930
+ * `retryTimes: 0` for the `call` and `query` requests themselves, which
931
+ * is what the setting is for.
932
+ * - A stale query signature. A query reply signed longer ago than the
933
+ * ingress expiry window no longer makes the agent sync its clock and ask
934
+ * again: it fails as a `Trust` error (`CertificateOutdatedErrorCode`),
935
+ * which a read classifies as a retryable `not_delivered`, so a browser
936
+ * read is asked again through `retryQuery` without a clock sync.
937
+ *
938
+ * Recovery from a refused ingress expiry (the agent syncs its clock once
939
+ * and sends again) does not depend on `retryTimes` and still happens.
940
+ */
941
+ agentFor(principal: string, context: AgentContext): Promise<HttpAgent>
942
+ }
943
+
944
+ const INTERNALS = new WeakMap<Client, ClientInternals>()
945
+
946
+ /**
947
+ * The internals of a client made by {@link createClient}.
948
+ *
949
+ * @throws TypeError for anything else.
950
+ */
951
+ export function internalsOf(client: Client): ClientInternals {
952
+ const internals = INTERNALS.get(client)
953
+ if (internals === undefined) {
954
+ throw new TypeError("[ic-reactor] expected a client made by createClient()")
955
+ }
956
+ return internals
957
+ }
958
+
959
+ // ---------------------------------------------------------------------------
960
+ // Option checks
961
+ // ---------------------------------------------------------------------------
962
+
963
+ const AUTH_MEMBERS = [
964
+ "getPrincipal",
965
+ "getStatus",
966
+ "getIdentity",
967
+ "subscribe",
968
+ "signIn",
969
+ "signOut",
970
+ ] as const
971
+
972
+ /** The options {@link createClient} takes. */
973
+ const OPTION_NAMES = [
974
+ "network",
975
+ "identity",
976
+ "auth",
977
+ "allowEnvConfig",
978
+ "fetch",
979
+ "maxDepth",
980
+ "maxElements",
981
+ ]
982
+
983
+ /**
984
+ * Checks what a JavaScript caller (or a cast) can get past the types: no
985
+ * option it does not have, exactly one of `identity` and `auth`, an identity
986
+ * that is one, an `auth` that is a factory, a `fetch` that is a function, and
987
+ * a `maxDepth` and `maxElements` `@meantjs/schema` would accept.
988
+ * {@link resolveNetwork} checks the network and `allowEnvConfig`.
989
+ */
990
+ function checkOptions(options: ClientOptions): void {
991
+ if (typeof options !== "object" || options === null) {
992
+ throw new TypeError(
993
+ "[ic-reactor] createClient() takes an options object: { network, identity } or { network, auth }."
994
+ )
995
+ }
996
+ checkKeys(options, OPTION_NAMES, "createClient()")
997
+ const { identity, auth, fetch, maxDepth, maxElements } = options as {
998
+ identity?: unknown
999
+ auth?: unknown
1000
+ fetch?: unknown
1001
+ maxDepth?: unknown
1002
+ maxElements?: unknown
1003
+ }
1004
+ if (fetch !== undefined && typeof fetch !== "function") {
1005
+ throw new TypeError(
1006
+ `[ic-reactor] fetch is a function, got ${typeof fetch}.`
1007
+ )
1008
+ }
1009
+ if (identity !== undefined && auth !== undefined) {
1010
+ throw new TypeError(
1011
+ "[ic-reactor] createClient() takes identity or auth, not both: identity fixes the caller, auth signs users in and out."
1012
+ )
1013
+ }
1014
+ if (identity === undefined && auth === undefined) {
1015
+ throw new TypeError(
1016
+ '[ic-reactor] createClient() needs to know who calls: identity: "anonymous" for a read-only client, ' +
1017
+ "identity: <Identity> for a fixed signer, or auth: (network) => new AuthClient(network) to sign users in, in a browser."
1018
+ )
1019
+ }
1020
+ if (auth !== undefined && typeof auth !== "function") {
1021
+ throw new TypeError(
1022
+ "[ic-reactor] auth is a factory, called only in a browser: auth: (network) => new AuthClient(network), not the AuthClient itself."
1023
+ )
1024
+ }
1025
+ if (
1026
+ identity !== undefined &&
1027
+ identity !== "anonymous" &&
1028
+ (typeof identity !== "object" ||
1029
+ identity === null ||
1030
+ typeof (identity as { getPrincipal?: unknown }).getPrincipal !==
1031
+ "function")
1032
+ ) {
1033
+ throw new TypeError(
1034
+ '[ic-reactor] identity is an Identity (such as an Ed25519KeyIdentity or a new AnonymousIdentity()) or "anonymous".'
1035
+ )
1036
+ }
1037
+ if (
1038
+ maxDepth !== undefined &&
1039
+ !(Number.isSafeInteger(maxDepth) && (maxDepth as number) >= 0)
1040
+ ) {
1041
+ throw new TypeError(
1042
+ `[ic-reactor] maxDepth is a non-negative safe integer, got ${String(maxDepth)}.`
1043
+ )
1044
+ }
1045
+ if (
1046
+ maxElements !== undefined &&
1047
+ !(Number.isSafeInteger(maxElements) && (maxElements as number) >= 0)
1048
+ ) {
1049
+ throw new TypeError(
1050
+ `[ic-reactor] maxElements is a non-negative safe integer, got ${String(maxElements)}.`
1051
+ )
1052
+ }
1053
+ }
1054
+
1055
+ /**
1056
+ * Whether two `signIn()` options are the same: the same value, or objects
1057
+ * with the same own enumerable keys whose values are the same (`Object.is`),
1058
+ * as two renders' `{ returnTo: "/account" }` are.
1059
+ */
1060
+ function sameOptions(a: unknown, b: unknown): boolean {
1061
+ if (Object.is(a, b)) return true
1062
+ if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false
1063
+ const first = a as Record<string, unknown>
1064
+ const second = b as Record<string, unknown>
1065
+ const keys = Object.keys(first)
1066
+ return (
1067
+ keys.length === Object.keys(second).length &&
1068
+ keys.every(
1069
+ (key) =>
1070
+ Object.prototype.hasOwnProperty.call(second, key) &&
1071
+ Object.is(first[key], second[key])
1072
+ )
1073
+ )
1074
+ }
1075
+
1076
+ /** Checks that an auth factory returned an {@link AuthLike}, naming what it lacks. */
1077
+ function checkAuth(auth: unknown): AuthLike {
1078
+ const missing =
1079
+ typeof auth === "object" && auth !== null
1080
+ ? AUTH_MEMBERS.filter(
1081
+ (name) =>
1082
+ typeof (auth as Record<string, unknown>)[name] !== "function"
1083
+ )
1084
+ : [...AUTH_MEMBERS]
1085
+ if (missing.length > 0) {
1086
+ throw new TypeError(
1087
+ `[ic-reactor] the auth factory returned something that is not an AuthLike: it has no ${missing
1088
+ .map((name) => `${name}()`)
1089
+ .join(", ")}.`
1090
+ )
1091
+ }
1092
+ return auth as AuthLike
1093
+ }
1094
+
1095
+ /**
1096
+ * Checks that `host`, read as `HttpAgent` reads it ({@link agentUrlOf}: a
1097
+ * host without a scheme is read against the page's protocol), is an `http:`
1098
+ * or `https:` URL, so a bad host is a `TypeError` from `createClient` rather
1099
+ * than a failure of every later call. `localhost:4943` reads as the scheme
1100
+ * `localhost:`. A host `"env"` chose came from the page's origin or from
1101
+ * `ICP_HOST` or `IC_HOST`, not from the caller, and the message says so.
1102
+ */
1103
+ function checkHost(host: string, fromEnv: boolean): void {
1104
+ const url = agentUrlOf(host)
1105
+ if (url?.protocol === "http:" || url?.protocol === "https:") return
1106
+ throw new TypeError(
1107
+ `[ic-reactor] the network host ${JSON.stringify(host)}${
1108
+ fromEnv
1109
+ ? ', which network "env" took from the page\'s origin or from ICP_HOST or IC_HOST,'
1110
+ : ""
1111
+ } is not a URL the agent can read (${
1112
+ url === undefined
1113
+ ? "it does not parse as one"
1114
+ : `its scheme is ${url.protocol}, and the agent calls over http: and https: only`
1115
+ }). Give one with a scheme, such as "https://icp-api.io"${
1116
+ fromEnv ? ", in ICP_HOST or as network: { host }" : ""
1117
+ }.`
1118
+ )
1119
+ }
1120
+
1121
+ let warnedEnvOffPage = false
1122
+
1123
+ /**
1124
+ * `network: "env"` outside a browser page works for `{ id }` targets only:
1125
+ * there is no `ic_env` cookie, so a `{ name }` target never resolves. Said
1126
+ * once per process, in development, not once per client: a server builds a
1127
+ * client per request.
1128
+ */
1129
+ function warnEnvOffPage(network: Network): void {
1130
+ if (
1131
+ network !== "env" ||
1132
+ !isServer() ||
1133
+ warnedEnvOffPage ||
1134
+ !isDevelopment()
1135
+ ) {
1136
+ return
1137
+ }
1138
+ warnedEnvOffPage = true
1139
+ console.warn(
1140
+ '[ic-reactor] createClient({ network: "env" }) outside a browser page: there is no ic_env cookie here, ' +
1141
+ "so every call to a { name } canister rejects invalid_args (canister_id_unresolved). " +
1142
+ "Call canisters by { id } from a server render, a route handler or a script. (Logged once, in development.)"
1143
+ )
1144
+ }
1145
+
1146
+ // ---------------------------------------------------------------------------
1147
+ // What @ic-reactor/react reads of a client
1148
+ // ---------------------------------------------------------------------------
1149
+
1150
+ /*
1151
+ * An internal contract between this module and `@ic-reactor/react`'s
1152
+ * `ReactorProvider`, not public API: no type names these keys and no entry
1153
+ * exports them. Changing a key or what it means breaks the provider.
1154
+ *
1155
+ * - `ic-reactor.clients.created`, on `globalThis`: how many clients
1156
+ * `createClient` has made in this realm, the client of each
1157
+ * `createTestClient()` among them: both are built by
1158
+ * {@link createClientWith}, which stamps every client it builds.
1159
+ * - `ic-reactor.client.serial`, on each client: that count once the client
1160
+ * was made, so the first client's is 1.
1161
+ * - `ic-reactor.client.disposed`, on each client: a getter, `true` once
1162
+ * `dispose()` has run.
1163
+ * - `ic-reactor.client.as`, on each client: a method that `useClient()`
1164
+ * calls with the client as `this` and the principal (text) a render
1165
+ * shows: the live caller's, or the server's anonymous one while a page
1166
+ * hydrates in a browser that holds a session. It returns a frozen view of
1167
+ * `this` for that principal (see `viewAs` in {@link createClientWith}),
1168
+ * the same one for the same principal while it is kept, or `this` itself
1169
+ * for a client built with `identity`, whose caller never changes.
1170
+ * - `ic-reactor.client.of`, on each view: the object the view was made over.
1171
+ * The provider holds that object, never a view, when its factory returns
1172
+ * one (a component's `useClient()` handed to a nested provider): a view
1173
+ * keeps its principal for good, so a tree on it would never follow the
1174
+ * caller.
1175
+ *
1176
+ * The provider reads the count, calls its `client` factory, and compares the
1177
+ * serial of the client it gets: a higher serial means the factory created the
1178
+ * client, and the provider disposes it when it unmounts; a client made before
1179
+ * (one at module scope) is borrowed, and the provider never disposes it. The
1180
+ * disposal flag lets it say so loudly when it is handed a borrowed client that
1181
+ * its owner already disposed.
1182
+ *
1183
+ * `Symbol.for` keys and a count kept on `globalThis`, so that two copies of
1184
+ * this package in one bundle count together, and the provider reads them
1185
+ * without importing anything of this package at run time. The keys on a
1186
+ * client or a view are non-enumerable, so spreading or logging one does not
1187
+ * show them.
1188
+ */
1189
+ const CLIENTS_CREATED = Symbol.for("ic-reactor.clients.created")
1190
+ const CLIENT_SERIAL = Symbol.for("ic-reactor.client.serial")
1191
+ const CLIENT_DISPOSED = Symbol.for("ic-reactor.client.disposed")
1192
+ const CLIENT_AS = Symbol.for("ic-reactor.client.as")
1193
+ const CLIENT_OF = Symbol.for("ic-reactor.client.of")
1194
+
1195
+ /** Counts one more client in this realm, and returns the count. */
1196
+ function nextSerial(): number {
1197
+ const count = (globalThis as { [CLIENTS_CREATED]?: unknown })[CLIENTS_CREATED]
1198
+ const serial = (typeof count === "number" ? count : 0) + 1
1199
+ try {
1200
+ Object.defineProperty(globalThis, CLIENTS_CREATED, {
1201
+ value: serial,
1202
+ writable: true,
1203
+ configurable: true,
1204
+ })
1205
+ } catch {
1206
+ // A frozen global (an SES lockdown) keeps no count. The provider then
1207
+ // reads 0 before every factory call and owns every client it is given.
1208
+ }
1209
+ return serial
1210
+ }
1211
+
1212
+ // ---------------------------------------------------------------------------
1213
+ // createClient
1214
+ // ---------------------------------------------------------------------------
1215
+
1216
+ const ANONYMOUS_STATE: AuthState = Object.freeze({
1217
+ status: "anonymous",
1218
+ principal: ANONYMOUS,
1219
+ })
1220
+
1221
+ const ANONYMOUS_CALLER: Caller = Object.freeze({
1222
+ principal: ANONYMOUS,
1223
+ authenticated: false,
1224
+ })
1225
+
1226
+ const sameState = (a: AuthState, b: AuthState): boolean =>
1227
+ a.status === b.status &&
1228
+ a.principal === b.principal &&
1229
+ a.expiresAt === b.expiresAt
1230
+
1231
+ /**
1232
+ * The longest delay `setTimeout` keeps: a longer one fires at once. A later
1233
+ * deadline is reached in steps of this length.
1234
+ */
1235
+ const MAX_TIMER_MS = 2 ** 31 - 1
1236
+
1237
+ /**
1238
+ * Who `auth` says calls: its principal only while its status is `signed-in`
1239
+ * and that principal is not the anonymous one. Every other state, including
1240
+ * one this version does not know, is anonymous.
1241
+ */
1242
+ function callerOf(auth: AuthLike): Caller {
1243
+ if (auth.getStatus().state !== "signed-in") return ANONYMOUS_CALLER
1244
+ const principal = auth.getPrincipal()?.toText()
1245
+ return principal === undefined || principal === ANONYMOUS
1246
+ ? ANONYMOUS_CALLER
1247
+ : { principal, authenticated: true }
1248
+ }
1249
+
1250
+ /** The {@link AuthState} `auth` stands for. See {@link AuthState} for the rules. */
1251
+ function stateOf(auth: AuthLike): AuthState {
1252
+ const caller = callerOf(auth)
1253
+ if (caller.authenticated) {
1254
+ const { expiresAtMs } = auth.getStatus()
1255
+ return Number.isFinite(expiresAtMs)
1256
+ ? {
1257
+ status: "signed-in",
1258
+ principal: caller.principal,
1259
+ expiresAt: expiresAtMs,
1260
+ }
1261
+ : { status: "signed-in", principal: caller.principal }
1262
+ }
1263
+ // A state the client does not know, such as one a later @icp-sdk/auth
1264
+ // adds, is anonymous.
1265
+ const { state } = auth.getStatus()
1266
+ return state === "expired" || state === "signed-in-elsewhere"
1267
+ ? { status: state as AuthState["status"], principal: ANONYMOUS }
1268
+ : ANONYMOUS_STATE
1269
+ }
1270
+
1271
+ /**
1272
+ * Creates a client: who calls, on which network, with which cache.
1273
+ *
1274
+ * Say who calls with exactly one of `identity` and `auth`. `auth` takes a
1275
+ * factory, which the client calls on first use and only in a browser, so the
1276
+ * same `createClient(...)` line runs in a server render (anonymous, no auth
1277
+ * built) and in the browser (signed in).
1278
+ *
1279
+ * Build one client per browser tab, or one per request on a server: a client's
1280
+ * cache and agents belong to whoever it calls as, and two clients share
1281
+ * neither.
1282
+ *
1283
+ * @throws TypeError for options that do not say who calls, say it twice, or
1284
+ * name a network or `maxDepth` that cannot be used; for an option it does not
1285
+ * have (in the options or in a network object); and for an `allowEnvConfig`
1286
+ * or `fetchRootKey` that is not a boolean, a `rootKey` that is not 133 bytes
1287
+ * in a `Uint8Array`, or a `fetch` that is not a function.
1288
+ *
1289
+ * @example
1290
+ * ```ts
1291
+ * import { AuthClient } from "@icp-sdk/auth/client"
1292
+ * import { createClient } from "@ic-reactor/core"
1293
+ *
1294
+ * const client = createClient({ network: "ic", auth: (network) => new AuthClient(network) })
1295
+ * client.subscribe(() => console.log(client.authState()))
1296
+ * await client.signIn()
1297
+ * ```
1298
+ */
1299
+ export function createClient(options: ClientOptions): Client {
1300
+ return createClientWith(options, {})
1301
+ }
1302
+
1303
+ /**
1304
+ * What the test client (`@ic-reactor/core/testing`) changes about how a client
1305
+ * runs, which no app may. Not part of {@link ClientOptions}, which is public,
1306
+ * and not exported from the package entry: {@link createClientWith} is the
1307
+ * only way in.
1308
+ */
1309
+ export interface ClientSeams {
1310
+ /**
1311
+ * Builds the auth factory on a server too. {@link createClient} builds it
1312
+ * only in a browser, so that one `createClient({ auth })` line is anonymous
1313
+ * in a server render and signed in in the browser. A test client lives in
1314
+ * Node, where a test signs users in and out and expects the calls to follow:
1315
+ * with the auth never built there, `auth.switchTo(2)` would change nothing a
1316
+ * call can see.
1317
+ */
1318
+ readonly authOnServer?: boolean
1319
+ }
1320
+
1321
+ /**
1322
+ * {@link createClient} with the {@link ClientSeams} a test client needs.
1323
+ * Internal: not exported from the package entry.
1324
+ */
1325
+ export function createClientWith(
1326
+ options: ClientOptions,
1327
+ seams: ClientSeams
1328
+ ): Client {
1329
+ checkOptions(options)
1330
+ const network = resolveNetwork(options.network, {
1331
+ allowEnvConfig: options.allowEnvConfig,
1332
+ })
1333
+ checkHost(network.host, options.network === "env")
1334
+ warnEnvOffPage(options.network)
1335
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH
1336
+ const maxElements = options.maxElements ?? DEFAULT_MAX_ELEMENTS
1337
+ const authFactory = options.auth
1338
+ const fixedIdentity =
1339
+ options.identity === undefined || options.identity === "anonymous"
1340
+ ? undefined
1341
+ : options.identity
1342
+
1343
+ // A user's `fetch` is called as a plain function. The agent would call it as
1344
+ // its own method, and a browser's native `fetch` throws "Illegal invocation"
1345
+ // when called on anything but `window`.
1346
+ const userFetch = options.fetch
1347
+ const agentFetch: typeof globalThis.fetch | undefined =
1348
+ userFetch === undefined
1349
+ ? undefined
1350
+ : (input: RequestInfo | URL, init?: RequestInit) => userFetch(input, init)
1351
+
1352
+ const mutationCallers = new WeakMap<object, Caller>()
1353
+ const ownMutationFns = new WeakSet<object>()
1354
+ const queryClient = new QueryClient({
1355
+ mutationCache: new MutationCache({
1356
+ // Only for a write of this client's `mutationOptions`: a mutation of
1357
+ // the app's never builds the auth, and never fails here. Before 5.89
1358
+ // TanStack passes no context.
1359
+ onMutate: (
1360
+ _vars: unknown,
1361
+ mutation: { readonly options: { readonly mutationFn?: unknown } },
1362
+ context?: unknown
1363
+ ) => {
1364
+ const fn = mutation.options.mutationFn
1365
+ if (typeof fn !== "function" || !ownMutationFns.has(fn)) return
1366
+ let caller: Caller
1367
+ try {
1368
+ caller = current()
1369
+ } catch {
1370
+ // The write's own mutationFn reads the caller again, and fails
1371
+ // as a call does.
1372
+ return
1373
+ }
1374
+ mutationCallers.set(mutation, caller)
1375
+ if (typeof context === "object" && context !== null) {
1376
+ mutationCallers.set(context, caller)
1377
+ }
1378
+ },
1379
+ }),
1380
+ defaultOptions: {
1381
+ queries: { retry: retryDefault },
1382
+ dehydrate: { serializeData },
1383
+ hydrate: { deserializeData },
1384
+ },
1385
+ })
1386
+
1387
+ /** Signs the anonymous principal's calls in `auth` mode and for `identity: "anonymous"`. */
1388
+ const anonymousIdentity = new AnonymousIdentity()
1389
+
1390
+ /** Who calls in `identity` mode; `undefined` in `auth` mode. */
1391
+ const fixedCaller: Caller | undefined =
1392
+ options.identity === undefined
1393
+ ? undefined
1394
+ : fixedIdentity === undefined
1395
+ ? ANONYMOUS_CALLER
1396
+ : Object.freeze({
1397
+ principal: fixedIdentity.getPrincipal().toText(),
1398
+ authenticated: true,
1399
+ })
1400
+
1401
+ /** The {@link AuthState} in `identity` mode; `undefined` in `auth` mode. */
1402
+ const fixedState: AuthState | undefined =
1403
+ fixedCaller === undefined
1404
+ ? undefined
1405
+ : fixedCaller.principal === ANONYMOUS
1406
+ ? ANONYMOUS_STATE
1407
+ : Object.freeze({
1408
+ status: "signed-in",
1409
+ principal: fixedCaller.principal,
1410
+ })
1411
+
1412
+ /**
1413
+ * One agent per principal, with the identity it signs with and the number of
1414
+ * the identity request that produced it (see `agentFor`).
1415
+ */
1416
+ const agents = new Map<
1417
+ string,
1418
+ { identity: Identity; agent: HttpAgent; asked: number }
1419
+ >()
1420
+ let identityRequests = 0
1421
+ const listeners = new Set<() => void>()
1422
+ let auth: AuthLike | undefined
1423
+ let unsubscribeAuth: (() => void) | undefined
1424
+ /**
1425
+ * What the auth factory threw, once it has, or the `TypeError` for a result
1426
+ * that is not an {@link AuthLike}: the client then runs anonymous, never calls
1427
+ * the factory again, and rejects `signIn()` and `signOut()` with it.
1428
+ */
1429
+ let authFailure: { readonly cause: unknown } | undefined
1430
+ /**
1431
+ * The `signIn()` calls started in the current synchronous run, with their
1432
+ * options, until a microtask queued by the run's first call clears them. A
1433
+ * later call with the same options as one of them returns its promise
1434
+ * instead of signing in again: React's `StrictMode` runs an effect twice in
1435
+ * one run, and a redirect sign-in (ICRC-167) that starts twice fails on the
1436
+ * load that comes back.
1437
+ */
1438
+ let signInsThisRun: Array<{
1439
+ readonly options: unknown
1440
+ readonly done: Promise<void>
1441
+ }> = []
1442
+ /** The timer set for the end of the session (see `scheduleExpiry`). */
1443
+ let expiryTimer: ReturnType<typeof setTimeout> | undefined
1444
+ /** Whether a `catchUp` notification is queued. */
1445
+ let catchUpQueued = false
1446
+ /** Whether this client said that its network has no identity provider. */
1447
+ let warnedAuthNetwork = false
1448
+ let disposed = false
1449
+ /** What {@link Client.authState} returns: replaced only when the state changes. */
1450
+ let snapshot: AuthState = fixedState ?? ANONYMOUS_STATE
1451
+ /** The state listeners were last told about, or the one they started with. */
1452
+ let notified: AuthState = snapshot
1453
+
1454
+ /**
1455
+ * The errors of reads a view built that were refused while someone else
1456
+ * was current, and left on a query with no data (see `createBuilders`).
1457
+ */
1458
+ const refusals = new WeakSet<object>()
1459
+
1460
+ /**
1461
+ * Clears the refusals of `principal`'s reads, now that it is current, from
1462
+ * the queries that still hold one and nothing else, before any listener
1463
+ * renders them: such a query reads as never fetched, so an observer fetches
1464
+ * it, where a suspense read would otherwise throw the refusal (TanStack does
1465
+ * not refetch an errored query on mount for one).
1466
+ */
1467
+ function clearRefusals(principal: string): void {
1468
+ const queries = queryClient
1469
+ .getQueryCache()
1470
+ .findAll({ queryKey: [KEY_ROOT, network.keySegment, principal] })
1471
+ for (const query of queries) {
1472
+ const { error, data, fetchStatus } = query.state
1473
+ if (
1474
+ error !== null &&
1475
+ refusals.has(error) &&
1476
+ data === undefined &&
1477
+ fetchStatus === "idle"
1478
+ ) {
1479
+ query.setState({ ...query.state, status: "pending", error: null })
1480
+ }
1481
+ }
1482
+ }
1483
+
1484
+ /**
1485
+ * Says, once per client and in development only, that sign-in cannot work
1486
+ * on this network: an auth built without an identity provider signs in with
1487
+ * mainnet's Internet Identity, whose delegations this replica rejects.
1488
+ */
1489
+ const warnNoProvider = (gap: string): void => {
1490
+ if (warnedAuthNetwork || !isDevelopment()) return
1491
+ warnedAuthNetwork = true
1492
+ console.warn(
1493
+ `[ic-reactor] sign-in cannot work on this network (${network.host}) without an identity provider: ${gap}. ` +
1494
+ "The auth factory was given no identityProvider, so an AuthClient built from it signs in with mainnet's " +
1495
+ "Internet Identity, whose delegations this replica rejects. Name the provider: " +
1496
+ "(network) => new AuthClient({ ...network, identityProvider: { authorizeUrl, canisterId } }). " +
1497
+ "(Logged once, in development.)"
1498
+ )
1499
+ }
1500
+
1501
+ /**
1502
+ * What the auth factory is handed (see `authNetworkFor`), with the client's
1503
+ * `fetch` in its `agentOptions` when one was given, so an `AuthClient`'s own
1504
+ * agents send as the client's do.
1505
+ *
1506
+ * Where no identity provider could be named, `identityProvider` is a getter
1507
+ * that is not enumerable: it returns `undefined`, and its first read warns.
1508
+ * So `new AuthClient(network)`, which reads it, warns; a factory that names
1509
+ * its own provider with `{ ...network, identityProvider }` never reads it,
1510
+ * since a spread copies only enumerable properties; and a factory that
1511
+ * ignores `network`, for an auth that is not Internet Identity, never does.
1512
+ */
1513
+ const authNetwork = (): AuthNetworkArgument => {
1514
+ const resolution = authNetworkFor(options.network, network)
1515
+ const agentOptions =
1516
+ agentFetch === undefined
1517
+ ? resolution.network.agentOptions
1518
+ : { ...resolution.network.agentOptions, fetch: agentFetch }
1519
+ const { gap } = resolution
1520
+ if (gap === undefined) return { ...resolution.network, agentOptions }
1521
+ const handed = { agentOptions }
1522
+ Object.defineProperty(handed, "identityProvider", {
1523
+ get: (): undefined => {
1524
+ warnNoProvider(gap)
1525
+ return undefined
1526
+ },
1527
+ enumerable: false,
1528
+ })
1529
+ return handed
1530
+ }
1531
+
1532
+ /**
1533
+ * The auth, built on first use: never in `identity` mode, never on a server
1534
+ * (unless the test client's seam says so), never after
1535
+ * {@link Client.dispose}.
1536
+ */
1537
+ const ensureAuth = (): AuthLike | undefined => {
1538
+ if (
1539
+ auth !== undefined ||
1540
+ authFailure !== undefined ||
1541
+ authFactory === undefined ||
1542
+ disposed ||
1543
+ (seams.authOnServer !== true && isServer())
1544
+ ) {
1545
+ return auth
1546
+ }
1547
+ let made: unknown
1548
+ let built: AuthLike
1549
+ try {
1550
+ made = authFactory(authNetwork())
1551
+ // A factory that returns no AuthLike (a Promise, from an `async`
1552
+ // factory) fails here too, and is not called again.
1553
+ built = checkAuth(made)
1554
+ } catch (cause) {
1555
+ // `new AuthClient()` reads `localStorage`, which throws where site data
1556
+ // is blocked or in a sandboxed frame. That must not take the public
1557
+ // reads down with it, nor run again on every render.
1558
+ authFailure = { cause }
1559
+ if (isDevelopment()) {
1560
+ console.error(
1561
+ "[ic-reactor] the auth factory threw: this client calls anonymously, and signIn() rejects with this error. (Logged once, in development.)",
1562
+ cause
1563
+ )
1564
+ }
1565
+ return undefined
1566
+ }
1567
+ auth = built
1568
+ const initial = stateOf(built)
1569
+ if (!sameState(initial, snapshot)) snapshot = Object.freeze(initial)
1570
+ notified = snapshot
1571
+ unsubscribeAuth = built.subscribe(onAuthChange)
1572
+ scheduleExpiry()
1573
+ return built
1574
+ }
1575
+
1576
+ /**
1577
+ * Sets one timer for the end of the auth's session, replacing the last one:
1578
+ * `AuthClient`'s status turns `expired` by the clock, with no notification,
1579
+ * so without it nobody would be told. A deadline that has passed while the
1580
+ * auth still says `signed-in` sets none: that auth does not expire by the
1581
+ * clock, and a timer would only fire again and again.
1582
+ */
1583
+ function scheduleExpiry(): void {
1584
+ clearTimeout(expiryTimer)
1585
+ expiryTimer = undefined
1586
+ if (disposed || auth === undefined) return
1587
+ const { state, expiresAtMs } = auth.getStatus()
1588
+ if (state !== "signed-in" && state !== "signed-in-elsewhere") return
1589
+ if (expiresAtMs === undefined || !Number.isFinite(expiresAtMs)) return
1590
+ const delay = expiresAtMs - Date.now()
1591
+ if (delay <= 0) return
1592
+ expiryTimer = setTimeout(
1593
+ () => {
1594
+ expiryTimer = undefined
1595
+ // A capped delay ends early: `onAuthChange` sets the next step.
1596
+ onAuthChange()
1597
+ },
1598
+ Math.min(delay, MAX_TIMER_MS)
1599
+ )
1600
+ // A session's end is no reason to keep a Node process (a test, a script
1601
+ // with the test client) alive.
1602
+ ;(expiryTimer as { unref?: () => void }).unref?.()
1603
+ }
1604
+
1605
+ /**
1606
+ * Tells the listeners, in a microtask, about a change a read noticed before
1607
+ * any notification did (a session that ended while the timer was held back,
1608
+ * as a browser does in a background tab). Queued rather than run, since the
1609
+ * read may be a render's.
1610
+ */
1611
+ const catchUp = (): void => {
1612
+ if (catchUpQueued || disposed) return
1613
+ catchUpQueued = true
1614
+ queueMicrotask(() => {
1615
+ catchUpQueued = false
1616
+ onAuthChange()
1617
+ })
1618
+ }
1619
+
1620
+ const current = (): Caller => {
1621
+ if (disposed) return ANONYMOUS_CALLER
1622
+ if (fixedCaller !== undefined) return fixedCaller
1623
+ const source = ensureAuth()
1624
+ if (source === undefined) return ANONYMOUS_CALLER
1625
+ const caller = callerOf(source)
1626
+ if (caller.principal !== notified.principal) catchUp()
1627
+ return caller
1628
+ }
1629
+
1630
+ /** Re-reads the state, replacing {@link snapshot} only when it changed. */
1631
+ const refresh = (): AuthState => {
1632
+ if (disposed || fixedState !== undefined) return snapshot
1633
+ const source = ensureAuth()
1634
+ const next = source === undefined ? ANONYMOUS_STATE : stateOf(source)
1635
+ if (!sameState(next, snapshot)) snapshot = Object.freeze(next)
1636
+ return snapshot
1637
+ }
1638
+
1639
+ /** Runs after each notification of the auth, and after a sign-in or sign-out. */
1640
+ function onAuthChange(): void {
1641
+ if (disposed) return
1642
+ const state = refresh()
1643
+ // An agent signs for one principal. Those that are no longer current are
1644
+ // dropped: a call made for them is cancelled before it needs one.
1645
+ for (const principal of agents.keys()) {
1646
+ if (principal !== state.principal) agents.delete(principal)
1647
+ }
1648
+ scheduleExpiry()
1649
+ if (state === notified) return
1650
+ const changed = state.principal !== notified.principal
1651
+ notified = state
1652
+ // A sign-out, an expired session or a switch of account removes the
1653
+ // reads made as anyone but the new caller and the anonymous one, before
1654
+ // a listener renders: they stay out of the next person's reach in a
1655
+ // shared browser, and out of a persister that saves the cache.
1656
+ if (changed) {
1657
+ queryClient.removeQueries({
1658
+ queryKey: [KEY_ROOT, network.keySegment],
1659
+ predicate: ({ queryKey: [, , caller] }) =>
1660
+ caller !== state.principal && caller !== ANONYMOUS,
1661
+ })
1662
+ }
1663
+ clearRefusals(state.principal)
1664
+ for (const listener of [...listeners]) {
1665
+ // A listener may dispose the client; the rest are then not told.
1666
+ if (disposed) return
1667
+ try {
1668
+ listener()
1669
+ } catch (error) {
1670
+ // One listener's bug must not keep the others from hearing it.
1671
+ queueMicrotask(() => {
1672
+ throw error
1673
+ })
1674
+ }
1675
+ }
1676
+ }
1677
+
1678
+ const identityFor = (principal: string): Promise<Identity> => {
1679
+ if (fixedIdentity !== undefined) return Promise.resolve(fixedIdentity)
1680
+ if (principal === ANONYMOUS) return Promise.resolve(anonymousIdentity)
1681
+ // `current()` named this principal, so the auth exists and is signed in.
1682
+ return (auth as AuthLike).getIdentity()
1683
+ }
1684
+
1685
+ const agentFor = async (
1686
+ principal: string,
1687
+ context: AgentContext
1688
+ ): Promise<HttpAgent> => {
1689
+ const where = { method: context.method, canisterId: context.canisterId }
1690
+ const assertCurrent = (): void => {
1691
+ if (disposed) {
1692
+ throw createReactorError("cancelled", {
1693
+ ...where,
1694
+ code: "client_disposed",
1695
+ reason: "the client was disposed before the call was sent",
1696
+ })
1697
+ }
1698
+ const now = current().principal
1699
+ if (now !== principal) {
1700
+ throw createReactorError("cancelled", {
1701
+ ...where,
1702
+ code: "caller_changed",
1703
+ reason: `the call was made as ${principal}, but the caller is now ${now}; it was not sent`,
1704
+ })
1705
+ }
1706
+ }
1707
+
1708
+ assertCurrent()
1709
+ const asked = ++identityRequests
1710
+ let identity: Identity
1711
+ try {
1712
+ identity = await identityFor(principal)
1713
+ } catch (cause) {
1714
+ assertCurrent()
1715
+ throw createReactorError("unauthenticated", {
1716
+ ...where,
1717
+ code: "identity_unavailable",
1718
+ reason: `the auth could not provide the identity of ${principal}`,
1719
+ cause,
1720
+ })
1721
+ }
1722
+ // The identity took time to arrive: whoever is current now decides.
1723
+ assertCurrent()
1724
+ const signer = identity.getPrincipal().toText()
1725
+ if (signer !== principal) {
1726
+ throw createReactorError("cancelled", {
1727
+ ...where,
1728
+ code: "caller_changed",
1729
+ reason: `the call was made as ${principal}, but the auth's identity is ${signer}; it was not sent`,
1730
+ })
1731
+ }
1732
+
1733
+ // The same identity object reuses the agent. A newer one (a renewed
1734
+ // delegation) replaces it. An older one that arrives after a newer one was
1735
+ // installed, because its request was answered late, gets the newer agent:
1736
+ // a replaced agent is never handed out again.
1737
+ const entry = agents.get(principal)
1738
+ if (
1739
+ entry !== undefined &&
1740
+ (entry.identity === identity || entry.asked > asked)
1741
+ ) {
1742
+ return entry.agent
1743
+ }
1744
+ const agent = HttpAgent.createSync({
1745
+ ...agentOptionsFor(network),
1746
+ // The agent signs outside its own error handling: a signer that fails
1747
+ // is thrown as a request that could not be signed (`notSigned`), never
1748
+ // as a call whose outcome is unknown.
1749
+ identity: {
1750
+ getPrincipal: () => identity.getPrincipal(),
1751
+ transformRequest: async (request) => {
1752
+ try {
1753
+ return await identity.transformRequest(request)
1754
+ } catch (cause) {
1755
+ throw notSigned(cause)
1756
+ }
1757
+ },
1758
+ },
1759
+ fetch: agentFetch,
1760
+ // The agent re-sends a request on its own after a fetch failure or a
1761
+ // non-2xx status and reports only the last status, so a 429 after a
1762
+ // lost first attempt would read as "never delivered" and be re-sent
1763
+ // again. With none of its own, every re-send is the call path's,
1764
+ // decided from what this attempt proves (see `retryUpdate`). It also
1765
+ // ends update polling at the first failed `read_state`; see
1766
+ // `ClientInternals.agentFor` for how the call path recovers from that.
1767
+ retryTimes: 0,
1768
+ })
1769
+ agents.set(principal, { identity, agent, asked })
1770
+ return agent
1771
+ }
1772
+
1773
+ /** The auth a sign-in or sign-out goes through, or why there is none. */
1774
+ const authFor = (call: "signIn" | "signOut"): AuthLike => {
1775
+ if (disposed) {
1776
+ throw new Error(`[ic-reactor] ${call}() on a disposed client.`)
1777
+ }
1778
+ if (authFactory === undefined) {
1779
+ throw new TypeError(
1780
+ `[ic-reactor] ${call}() needs a client built with auth; this one was built with identity, which never changes. ` +
1781
+ "Build it with auth: (network) => new AuthClient(network) to sign users in and out."
1782
+ )
1783
+ }
1784
+ const source = ensureAuth()
1785
+ // The factory threw: what it threw is the reason there is no sign-in.
1786
+ if (authFailure !== undefined) throw authFailure.cause
1787
+ if (source === undefined) {
1788
+ throw new Error(
1789
+ `[ic-reactor] ${call}() runs only in a browser: on a server the client is anonymous and never builds its auth.`
1790
+ )
1791
+ }
1792
+ return source
1793
+ }
1794
+
1795
+ const internals: ClientInternals = Object.freeze({
1796
+ network,
1797
+ maxDepth,
1798
+ maxElements,
1799
+ current,
1800
+ disposed: () => disposed,
1801
+ mutationCallers,
1802
+ ownMutationFns,
1803
+ offered: new WeakMap(),
1804
+ agentFor,
1805
+ })
1806
+
1807
+ /** The views of {@link viewAs}, by the object they were made over and their principal. */
1808
+ const views = new WeakMap<object, Map<string, Client>>()
1809
+
1810
+ /**
1811
+ * A view of `this` (the client, or an object over it) for a render that
1812
+ * shows `principal`: the live caller, or someone else while the live caller
1813
+ * moved on, such as the server's anonymous caller while a page hydrates in
1814
+ * a browser that holds a session. `useClient()` returns one on every
1815
+ * render, so that the object a component memoizes on changes exactly when
1816
+ * the caller it renders does (a sign-in, a switch, a sign-out).
1817
+ *
1818
+ * Its `queryKey` and `queryOptions` build for `principal`, so they find
1819
+ * what a server prefetched as that caller and render the markup the server
1820
+ * wrote; `caller()` and `authState()` say who that is (`authState()` is the
1821
+ * client's own state while `principal` is the live caller, so an expired
1822
+ * session reads as expired; otherwise `"anonymous"` for the anonymous
1823
+ * principal and `"signed-in"` for any other). The read options are
1824
+ * the client's own but for the key: a read they build asks `agentFor` for
1825
+ * `principal`'s agent, so while someone else is current it is cancelled
1826
+ * (`caller_changed`) and never sent. Its `mutationOptions` are the
1827
+ * client's own but for the `mutationKey`, which ends with `principal`, and
1828
+ * the mutation function, which sends only as `principal`: while someone
1829
+ * else is current it rejects `cancelled` (`caller_changed`,
1830
+ * `mayHaveExecuted: false`) and sends nothing, so a write from a render
1831
+ * that a switch has not reached yet never goes out as the new caller. Every other member is `this`'s own: canisters (made for
1832
+ * this client, so either one accepts them) and their direct calls,
1833
+ * `resendOf`'s `send` and `func`, which sign as the caller current when
1834
+ * they run, the `QueryClient`, `subscribe`, `signIn`, `signOut` and
1835
+ * `dispose`.
1836
+ *
1837
+ * One view per object and principal, so that a component's dependencies are
1838
+ * the same from one render to the next. A view already made is handed back
1839
+ * without reading who calls. Making a view drops every other one but the
1840
+ * anonymous view and the live caller's, so switching accounts leaves
1841
+ * nothing behind. Until then, the view of a user who signed out, or whose
1842
+ * session expired, is kept, and that user signing in again gets the same
1843
+ * view back. A client built with `identity` has one caller for good, the
1844
+ * one its server render built keys for too: it returns `this`.
1845
+ *
1846
+ * A view carries the object it was made over (`CLIENT_OF`), which
1847
+ * `ReactorProvider` holds in its place, and asked for a view of its own it
1848
+ * makes one of that object.
1849
+ */
1850
+ function viewAs(this: unknown, principal: string): Client {
1851
+ const self = (
1852
+ typeof this === "object" && this !== null ? this : client
1853
+ ) as Client & { readonly [CLIENT_OF]?: Client }
1854
+ // A view of a view is a view of what the first was made over.
1855
+ const base = self[CLIENT_OF] ?? self
1856
+ if (fixedCaller !== undefined) return base
1857
+ let byPrincipal = views.get(base)
1858
+ if (byPrincipal === undefined) views.set(base, (byPrincipal = new Map()))
1859
+ let view = byPrincipal.get(principal)
1860
+ if (view === undefined) {
1861
+ // The views are pruned only when one is made, since `useClient()` asks
1862
+ // for two on every render and reading the live caller converts its
1863
+ // principal to text each time (#977). Every miss prunes, so the map
1864
+ // holds at most the anonymous view, the live caller's and this one.
1865
+ const live = current().principal
1866
+ for (const kept of byPrincipal.keys()) {
1867
+ if (kept !== principal && kept !== live && kept !== ANONYMOUS) {
1868
+ byPrincipal.delete(kept)
1869
+ }
1870
+ }
1871
+ const caller: Caller = Object.freeze({
1872
+ principal,
1873
+ authenticated: principal !== ANONYMOUS,
1874
+ })
1875
+ const state: AuthState =
1876
+ principal === ANONYMOUS
1877
+ ? ANONYMOUS_STATE
1878
+ : Object.freeze({ status: "signed-in", principal })
1879
+ const { queryKey, queryOptions } = createBuilders(
1880
+ internals,
1881
+ () => client,
1882
+ () => caller,
1883
+ refusals
1884
+ )
1885
+ const own = base.mutationOptions as unknown as (
1886
+ canister: unknown,
1887
+ method: unknown,
1888
+ options?: unknown
1889
+ ) => {
1890
+ mutationKey: readonly unknown[]
1891
+ mutationFn: (vars: unknown, context?: unknown) => Promise<unknown>
1892
+ }
1893
+ // The client's options, whose checks run as they are built, with a
1894
+ // mutation function that sends as `principal` or not at all. The check
1895
+ // and the client's own function, which reads the caller it signs as,
1896
+ // run in one synchronous step; `agentFor` checks again before sending.
1897
+ //
1898
+ // The key ends with `principal`. TanStack hands a pending run the
1899
+ // options of a later render only when their `mutationKey` is the same;
1900
+ // with another one it detaches the observer and the run keeps the
1901
+ // options it was pressed with. So a press handled before a switch
1902
+ // reached its render is checked against its own principal, and refused,
1903
+ // on every TanStack version and whatever the QueryClient.
1904
+ const mutationOptions = (
1905
+ canister: unknown,
1906
+ method: unknown,
1907
+ options?: unknown
1908
+ ) => {
1909
+ const built = own(canister, method, options)
1910
+ const mutationFn = async function (
1911
+ this: unknown,
1912
+ vars: unknown,
1913
+ context?: unknown
1914
+ ) {
1915
+ const now = current().principal
1916
+ if (now !== principal) {
1917
+ const target = recordOf(
1918
+ canister,
1919
+ internals,
1920
+ "client.mutationOptions()"
1921
+ ).resolve()
1922
+ const refusal = createReactorError("cancelled", {
1923
+ method: String(method),
1924
+ canisterId: target.ok ? target.id : target.slot,
1925
+ code: "caller_changed",
1926
+ reason: `the write was made as ${principal}, but the caller is now ${now}; it was not sent`,
1927
+ })
1928
+ // A re-send `resendOf` offered leaves its write unknown, as the
1929
+ // call path does. Marked with the canister, as a call's error
1930
+ // is, so that `isReactorError(error, canister, method)` matches.
1931
+ const again = internals.offered.get(vars as object)
1932
+ throw fromCanister(
1933
+ again === undefined ? refusal : keptUnknown(again, refusal),
1934
+ canister as object
1935
+ )
1936
+ }
1937
+ // `this` is the options TanStack called it on, by which the
1938
+ // client's function finds the caller of a run with no context.
1939
+ return built.mutationFn.call(this, vars, context)
1940
+ }
1941
+ // The client's `MutationCache` reads the caller of a run of this
1942
+ // function too, as it does for the client's own.
1943
+ ownMutationFns.add(mutationFn)
1944
+ return {
1945
+ ...built,
1946
+ mutationKey: [...built.mutationKey, principal] as const,
1947
+ mutationFn,
1948
+ }
1949
+ }
1950
+ view = Object.freeze(
1951
+ Object.create(base, {
1952
+ caller: { value: () => principal },
1953
+ // The live state while `principal` is the live caller, so that a
1954
+ // view of the caller signed in now says what the client says (an
1955
+ // expired session included, which calls as the anonymous one).
1956
+ authState: {
1957
+ value: () => {
1958
+ const live = refresh()
1959
+ return live.principal === principal ? live : state
1960
+ },
1961
+ },
1962
+ queryKey: { value: queryKey },
1963
+ queryOptions: { value: queryOptions },
1964
+ mutationOptions: { value: mutationOptions },
1965
+ [CLIENT_OF]: { value: base },
1966
+ }) as Client
1967
+ )
1968
+ byPrincipal.set(principal, view)
1969
+ }
1970
+ return view
1971
+ }
1972
+
1973
+ const members: Client = {
1974
+ ...createBuilders(internals, () => client),
1975
+ network: network.keySegment,
1976
+ queryClient,
1977
+ canisterId: (target) =>
1978
+ resolveCanisterId(
1979
+ readTarget(target, "client.canisterId()").target,
1980
+ network
1981
+ ),
1982
+ caller: () => current().principal,
1983
+ authState(): AuthState {
1984
+ const state = refresh()
1985
+ if (state !== notified) catchUp()
1986
+ return state
1987
+ },
1988
+ subscribe(listener: () => void): () => void {
1989
+ if (disposed) return () => {}
1990
+ ensureAuth()
1991
+ listeners.add(listener)
1992
+ return () => {
1993
+ listeners.delete(listener)
1994
+ }
1995
+ },
1996
+ signIn(signInOptions?: unknown): Promise<void> {
1997
+ const started = signInsThisRun.find((earlier) =>
1998
+ sameOptions(earlier.options, signInOptions)
1999
+ )
2000
+ if (started) return started.done
2001
+ const done = (async () => {
2002
+ const source = authFor("signIn")
2003
+ try {
2004
+ await source.signIn(signInOptions)
2005
+ } finally {
2006
+ onAuthChange()
2007
+ }
2008
+ })()
2009
+ // Every sign-in started in this run is done joining when it ends. A
2010
+ // promise job, which fake timers do not replace as they can
2011
+ // `queueMicrotask`.
2012
+ if (signInsThisRun.length === 0) {
2013
+ void Promise.resolve().then(() => (signInsThisRun = []))
2014
+ }
2015
+ signInsThisRun.push({ options: signInOptions, done })
2016
+ return done
2017
+ },
2018
+ async signOut(signOutOptions?: unknown): Promise<void> {
2019
+ const source = authFor("signOut")
2020
+ try {
2021
+ await source.signOut(signOutOptions)
2022
+ } finally {
2023
+ onAuthChange()
2024
+ }
2025
+ },
2026
+ dispose(): void {
2027
+ if (disposed) return
2028
+ disposed = true
2029
+ const source = auth
2030
+ const unsubscribe = unsubscribeAuth
2031
+ auth = undefined
2032
+ unsubscribeAuth = undefined
2033
+ listeners.clear()
2034
+ signInsThisRun = []
2035
+ agents.clear()
2036
+ clearTimeout(expiryTimer)
2037
+ expiryTimer = undefined
2038
+ snapshot = notified = ANONYMOUS_STATE
2039
+ queryClient.clear()
2040
+ unsubscribe?.()
2041
+ source?.dispose?.()
2042
+ },
2043
+ }
2044
+ // The stamps and the seam `@ic-reactor/react` reads (see `CLIENTS_CREATED`), defined
2045
+ // before the freeze: a frozen object takes no new property. The stamps are
2046
+ // not enumerable, so a spread copy of a client has no serial and is not
2047
+ // taken for the client it copies. The seam is: a copy (`{ ...client }`, a
2048
+ // test's double) keeps it, and its views are made over the copy, whose
2049
+ // members are the client's own, so a provider given a copy hydrates as one
2050
+ // given the client does.
2051
+ const client: Client = Object.freeze(
2052
+ Object.defineProperties(members, {
2053
+ [CLIENT_SERIAL]: { value: nextSerial() },
2054
+ [CLIENT_DISPOSED]: { get: () => disposed },
2055
+ [CLIENT_AS]: { value: viewAs, enumerable: true },
2056
+ })
2057
+ )
2058
+
2059
+ INTERNALS.set(client, internals)
2060
+ return client
2061
+ }