@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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
- package/.github/actions/setup-rust-wasm/action.yml +25 -0
- package/.github/dependabot.yml +107 -0
- package/.github/workflows/auth-latest.yml +67 -0
- package/.github/workflows/b3pay-site.yml +49 -0
- package/.github/workflows/ci.yml +430 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/dependency-review.yml +68 -0
- package/.github/workflows/docs-check.yml +98 -0
- package/.github/workflows/docs.yml +265 -0
- package/.github/workflows/e2e.yml +90 -0
- package/.github/workflows/examples-published.yml +97 -0
- package/.github/workflows/release.yml +326 -0
- package/.husky/pre-commit +12 -0
- package/.prettierignore +109 -0
- package/.prettierrc +6 -0
- package/AGENTS.md +103 -0
- package/CHANGELOG.md +3268 -0
- package/CLAUDE.md +93 -0
- package/CNAME +1 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +433 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/SECURITY.md +74 -0
- package/b3pay-module.json +194 -0
- package/docs/.remarkrc.json +10 -0
- package/docs/README.md +64 -0
- package/docs/astro.config.mjs +205 -0
- package/docs/favicon.svg +15 -0
- package/docs/package.json +39 -0
- package/docs/public/favicon.svg +15 -0
- package/docs/public/ic-reactor-social-card.png +0 -0
- package/docs/scripts/b3pay-static.mjs +22 -0
- package/docs/scripts/check-example-docs.mjs +214 -0
- package/docs/scripts/check-links.mjs +212 -0
- package/docs/scripts/fix-dist-links.js +125 -0
- package/docs/scripts/fix-frontmatter.js +71 -0
- package/docs/scripts/remove-redundant-h1.cjs +48 -0
- package/docs/scripts/rewrite-typedoc-links.js +110 -0
- package/docs/scripts/split-index.js +69 -0
- package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
- package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
- package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
- package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
- package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
- package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
- package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
- package/docs/src/components/LibraryCards.astro +172 -0
- package/docs/src/components/LibraryIcon.astro +28 -0
- package/docs/src/components/SiteTitle.astro +241 -0
- package/docs/src/content/docs/404.mdx +19 -0
- package/docs/src/content/docs/build/agent-tools.mdx +593 -0
- package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
- package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
- package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
- package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
- package/docs/src/content/docs/examples/index.mdx +38 -0
- package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
- package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
- package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
- package/docs/src/content/docs/guides/auth.mdx +1181 -0
- package/docs/src/content/docs/guides/client.mdx +551 -0
- package/docs/src/content/docs/guides/deploy.mdx +180 -0
- package/docs/src/content/docs/guides/errors.mdx +475 -0
- package/docs/src/content/docs/guides/getting-started.mdx +620 -0
- package/docs/src/content/docs/guides/reads.mdx +586 -0
- package/docs/src/content/docs/guides/ssr.mdx +529 -0
- package/docs/src/content/docs/guides/testing.mdx +401 -0
- package/docs/src/content/docs/guides/values.mdx +261 -0
- package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
- package/docs/src/content/docs/guides/writes.mdx +572 -0
- package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
- package/docs/src/content/docs/index.mdx +65 -0
- package/docs/src/content/docs/migrating-from-3.mdx +535 -0
- package/docs/src/content/docs/packages/core.mdx +186 -0
- package/docs/src/content/docs/packages/mcp.mdx +163 -0
- package/docs/src/content/docs/packages/react.mdx +207 -0
- package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
- package/docs/src/content/docs/versioning.mdx +104 -0
- package/docs/src/content.config.ts +7 -0
- package/docs/src/styles/custom.css +189 -0
- package/docs/tsconfig.typedoc.json +26 -0
- package/docs/typedoc-entry.ts +17 -0
- package/docs/typedoc.json +37 -0
- package/e2e/Cargo.lock +646 -0
- package/e2e/Cargo.toml +3 -0
- package/e2e/README.md +101 -0
- package/e2e/frontends/build.mjs +94 -0
- package/e2e/frontends/index.html +10 -0
- package/e2e/global-setup.ts +65 -0
- package/e2e/icp.yaml +38 -0
- package/e2e/package.json +40 -0
- package/e2e/setup.ts +6 -0
- package/e2e/src/actor/Cargo.toml +14 -0
- package/e2e/src/actor/hello_actor.did +10 -0
- package/e2e/src/actor/src/lib.rs +73 -0
- package/e2e/src/calls.test.ts +132 -0
- package/e2e/src/declarations/hello_actor.envelope.json +175 -0
- package/e2e/src/declarations/hello_actor.ts +15 -0
- package/e2e/src/errors.test.ts +107 -0
- package/e2e/src/frontend-headers.test.ts +253 -0
- package/e2e/src/identity.test.ts +61 -0
- package/e2e/src/react.test.tsx +59 -0
- package/e2e/src/replica.ts +70 -0
- package/e2e/src/vite-env.test.ts +108 -0
- package/e2e/test.sh +47 -0
- package/e2e/tsconfig.json +20 -0
- package/e2e/tsconfig.typecheck.json +9 -0
- package/e2e/vitest.config.ts +22 -0
- package/eslint.config.mjs +100 -0
- package/evals/PREREGISTRATION.md +1346 -0
- package/evals/README.md +756 -0
- package/evals/conditions/thin/condition.json +8 -0
- package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
- package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
- package/evals/conditions/thin/package.json +25 -0
- package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
- package/evals/conditions/thin-guide/condition.json +10 -0
- package/evals/conditions/thin-guide/docs/llms.txt +292 -0
- package/evals/conditions/v3/condition.json +9 -0
- package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
- package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
- package/evals/conditions/v3/docs/llms-full.txt +1053 -0
- package/evals/conditions/v3/package.json +26 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
- package/evals/conditions/v4/PORTING.md +384 -0
- package/evals/conditions/v4/README.md +106 -0
- package/evals/conditions/v4/condition.json +5 -0
- package/evals/conditions/v4/docs/llms.txt +323 -0
- package/evals/conditions/v4/package.json +27 -0
- package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
- package/evals/conditions/v4-proto/condition.json +6 -0
- package/evals/conditions/v4-proto/docs/llms.txt +291 -0
- package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
- package/evals/conditions/v4-proto/lib/package.json +27 -0
- package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
- package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
- package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
- package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
- package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
- package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
- package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
- package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
- package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
- package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
- package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
- package/evals/conditions/v4-proto/package.json +26 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
- package/evals/drive.mjs +1221 -0
- package/evals/gate.mjs +161 -0
- package/evals/harness/aggregate.mjs +534 -0
- package/evals/harness/aggregate.test.mjs +344 -0
- package/evals/harness/assemble.mjs +216 -0
- package/evals/harness/assemble.test.mjs +143 -0
- package/evals/harness/auth.mjs +91 -0
- package/evals/harness/check-docs.mjs +157 -0
- package/evals/harness/declarations/icrc1.did.d.ts +95 -0
- package/evals/harness/declarations/icrc1.did.js +140 -0
- package/evals/harness/drive-auth.test.mjs +584 -0
- package/evals/harness/drive.test.mjs +509 -0
- package/evals/harness/fake-auth.ts +67 -0
- package/evals/harness/fake-ledger.ts +178 -0
- package/evals/harness/gate-plan.mjs +104 -0
- package/evals/harness/gate-plan.test.mjs +243 -0
- package/evals/harness/icrc1.did +88 -0
- package/evals/harness/judge.mjs +63 -0
- package/evals/harness/judge.test.mjs +81 -0
- package/evals/harness/leak-scan.mjs +1373 -0
- package/evals/harness/leak-scan.test.mjs +1300 -0
- package/evals/harness/probes/amounts.ts +14 -0
- package/evals/harness/probes/node-tool.probe.ts +48 -0
- package/evals/harness/probes/probe.mjs +149 -0
- package/evals/harness/probes/react-wallet.probe.ts +96 -0
- package/evals/harness/public/support/auth.ts +38 -0
- package/evals/harness/public/support/fake-replica.d.ts +28 -0
- package/evals/harness/public/support/fake-replica.js +434 -0
- package/evals/harness/public/support/ledger.idl.d.ts +95 -0
- package/evals/harness/public/support/ledger.idl.js +140 -0
- package/evals/harness/public/support/world.ts +179 -0
- package/evals/harness/runs.mjs +139 -0
- package/evals/harness/sandbox.mjs +88 -0
- package/evals/harness/sandbox.test.mjs +134 -0
- package/evals/harness/ship.mjs +264 -0
- package/evals/harness/ship.test.mjs +552 -0
- package/evals/harness/world.ts +291 -0
- package/evals/package.json +26 -0
- package/evals/pnpm-lock.yaml +1537 -0
- package/evals/pnpm-workspace.yaml +12 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
- package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
- package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
- package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
- package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
- package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
- package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
- package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
- package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
- package/evals/results/README.md +31 -0
- package/evals/runs-pilot.log +150 -0
- package/evals/runs-pilot2.log +150 -0
- package/evals/score.mjs +256 -0
- package/evals/setup.mjs +547 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
- package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
- package/evals/tasks/node-tool/prompt.md +55 -0
- package/evals/tasks/node-tool/prompt.minimal.md +30 -0
- package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
- package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
- package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
- package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
- package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
- package/evals/tasks/node-tool/starter/src/index.ts +7 -0
- package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
- package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
- package/evals/tasks/node-tool/task.json +63 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
- package/evals/tasks/react-wallet/prompt.md +88 -0
- package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
- package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
- package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
- package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
- package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
- package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
- package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
- package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
- package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
- package/evals/tasks/react-wallet/task.json +84 -0
- package/examples/canister-studio/.stackblitzrc +4 -0
- package/examples/canister-studio/README.md +183 -0
- package/examples/canister-studio/did/ckbtc_minter.did +806 -0
- package/examples/canister-studio/did/icp_ledger.did +586 -0
- package/examples/canister-studio/did/icrc1.did +88 -0
- package/examples/canister-studio/package.json +45 -0
- package/examples/canister-studio/scripts/smoke.ts +188 -0
- package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
- package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
- package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
- package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
- package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
- package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
- package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
- package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
- package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
- package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
- package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
- package/examples/canister-studio/src/calls/dedupe.ts +189 -0
- package/examples/canister-studio/src/calls/error-info.ts +168 -0
- package/examples/canister-studio/src/calls/history.test.ts +284 -0
- package/examples/canister-studio/src/calls/history.ts +287 -0
- package/examples/canister-studio/src/calls/kinds.ts +68 -0
- package/examples/canister-studio/src/calls/prepare.ts +32 -0
- package/examples/canister-studio/src/calls/query-options.ts +30 -0
- package/examples/canister-studio/src/calls/read.ts +47 -0
- package/examples/canister-studio/src/calls/readback.ts +100 -0
- package/examples/canister-studio/src/calls/record.ts +123 -0
- package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
- package/examples/canister-studio/src/calls/share.ts +46 -0
- package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
- package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
- package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
- package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
- package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
- package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
- package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
- package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
- package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
- package/examples/canister-studio/src/components/AppShell.tsx +29 -0
- package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
- package/examples/canister-studio/src/components/Badge.tsx +79 -0
- package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
- package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
- package/examples/canister-studio/src/components/Card.tsx +64 -0
- package/examples/canister-studio/src/components/Code.tsx +101 -0
- package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
- package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
- package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
- package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
- package/examples/canister-studio/src/components/MethodList.tsx +244 -0
- package/examples/canister-studio/src/components/Notice.tsx +36 -0
- package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
- package/examples/canister-studio/src/components/RouteError.tsx +42 -0
- package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
- package/examples/canister-studio/src/components/Spinner.tsx +33 -0
- package/examples/canister-studio/src/components/Tabs.tsx +96 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
- package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
- package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
- package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
- package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
- package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
- package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
- package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
- package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
- package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
- package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
- package/examples/canister-studio/src/components/canister-names.ts +11 -0
- package/examples/canister-studio/src/components/download.ts +34 -0
- package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
- package/examples/canister-studio/src/components/helpers.test.ts +107 -0
- package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
- package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
- package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
- package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
- package/examples/canister-studio/src/components/home/featured.ts +64 -0
- package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
- package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
- package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
- package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
- package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
- package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
- package/examples/canister-studio/src/components/modes.ts +31 -0
- package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
- package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
- package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
- package/examples/canister-studio/src/components/playground/sample.ts +43 -0
- package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
- package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
- package/examples/canister-studio/src/components/provenance.ts +38 -0
- package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
- package/examples/canister-studio/src/components/use-task.ts +56 -0
- package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
- package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
- package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
- package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
- package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
- package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
- package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
- package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
- package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
- package/examples/canister-studio/src/forms/context.ts +62 -0
- package/examples/canister-studio/src/forms/convert.ts +131 -0
- package/examples/canister-studio/src/forms/edge.test.ts +252 -0
- package/examples/canister-studio/src/forms/engine.test.ts +358 -0
- package/examples/canister-studio/src/forms/engine.ts +1492 -0
- package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
- package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
- package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
- package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
- package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
- package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
- package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
- package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
- package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
- package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
- package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
- package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
- package/examples/canister-studio/src/forms/hints.ts +128 -0
- package/examples/canister-studio/src/forms/json.ts +119 -0
- package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
- package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
- package/examples/canister-studio/src/interface/browser.test.ts +23 -0
- package/examples/canister-studio/src/interface/browser.ts +153 -0
- package/examples/canister-studio/src/interface/cache.ts +68 -0
- package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
- package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
- package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
- package/examples/canister-studio/src/interface/compile.server.ts +262 -0
- package/examples/canister-studio/src/interface/compile.test.ts +108 -0
- package/examples/canister-studio/src/interface/compile.ts +305 -0
- package/examples/canister-studio/src/interface/docs.test.ts +75 -0
- package/examples/canister-studio/src/interface/docs.ts +100 -0
- package/examples/canister-studio/src/interface/functions.test.ts +42 -0
- package/examples/canister-studio/src/interface/functions.ts +200 -0
- package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
- package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
- package/examples/canister-studio/src/interface/live-loader.ts +364 -0
- package/examples/canister-studio/src/interface/load.test.ts +328 -0
- package/examples/canister-studio/src/interface/load.ts +249 -0
- package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
- package/examples/canister-studio/src/interface/read-candid.ts +233 -0
- package/examples/canister-studio/src/interface/server-result.ts +42 -0
- package/examples/canister-studio/src/interface/typed.ts +68 -0
- package/examples/canister-studio/src/interface/types.ts +121 -0
- package/examples/canister-studio/src/lib/format.test.ts +107 -0
- package/examples/canister-studio/src/lib/format.ts +139 -0
- package/examples/canister-studio/src/lib/hex.test.ts +43 -0
- package/examples/canister-studio/src/lib/hex.ts +33 -0
- package/examples/canister-studio/src/lib/ids.test.ts +70 -0
- package/examples/canister-studio/src/lib/ids.ts +93 -0
- package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
- package/examples/canister-studio/src/lib/reactor.ts +55 -0
- package/examples/canister-studio/src/lib/registry.test.ts +74 -0
- package/examples/canister-studio/src/lib/registry.ts +238 -0
- package/examples/canister-studio/src/lib/schema.ts +148 -0
- package/examples/canister-studio/src/routeTree.gen.ts +227 -0
- package/examples/canister-studio/src/router.test.ts +290 -0
- package/examples/canister-studio/src/router.tsx +127 -0
- package/examples/canister-studio/src/routes/__root.tsx +58 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
- package/examples/canister-studio/src/routes/history.tsx +55 -0
- package/examples/canister-studio/src/routes/index.tsx +169 -0
- package/examples/canister-studio/src/routes/playground.tsx +286 -0
- package/examples/canister-studio/src/routes/safety.tsx +57 -0
- package/examples/canister-studio/src/routes/wallet.tsx +213 -0
- package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
- package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
- package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
- package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
- package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
- package/examples/canister-studio/src/safety/lab-run.ts +283 -0
- package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
- package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
- package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
- package/examples/canister-studio/src/safety/scenarios.ts +924 -0
- package/examples/canister-studio/src/styles.css +3453 -0
- package/examples/canister-studio/src/styles.test.ts +46 -0
- package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
- package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
- package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
- package/examples/canister-studio/src/values/Inspector.tsx +133 -0
- package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
- package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
- package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
- package/examples/canister-studio/src/values/candid-text.ts +479 -0
- package/examples/canister-studio/src/values/hints.ts +58 -0
- package/examples/canister-studio/src/values/json.test.ts +66 -0
- package/examples/canister-studio/src/values/json.ts +31 -0
- package/examples/canister-studio/src/values/leaves.tsx +309 -0
- package/examples/canister-studio/src/values/schema.test.ts +123 -0
- package/examples/canister-studio/src/values/schema.ts +78 -0
- package/examples/canister-studio/src/values/test-router.tsx +21 -0
- package/examples/canister-studio/src/values/tree.tsx +670 -0
- package/examples/canister-studio/src/values/use-sha256.ts +29 -0
- package/examples/canister-studio/tsconfig.json +20 -0
- package/examples/canister-studio/vite.config.ts +50 -0
- package/examples/canister-studio/vitest.config.ts +20 -0
- package/examples/icrc-ledger/.stackblitzrc +4 -0
- package/examples/icrc-ledger/README.md +100 -0
- package/examples/icrc-ledger/ckbtc_minter.did +12 -0
- package/examples/icrc-ledger/icp_ledger.did +147 -0
- package/examples/icrc-ledger/icrc1.did +88 -0
- package/examples/icrc-ledger/index.html +13 -0
- package/examples/icrc-ledger/package.json +40 -0
- package/examples/icrc-ledger/scripts/smoke.ts +151 -0
- package/examples/icrc-ledger/src/App.tsx +71 -0
- package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
- package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
- package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
- package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
- package/examples/icrc-ledger/src/blocks.test.ts +247 -0
- package/examples/icrc-ledger/src/blocks.ts +140 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
- package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
- package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
- package/examples/icrc-ledger/src/format.test.ts +41 -0
- package/examples/icrc-ledger/src/format.ts +68 -0
- package/examples/icrc-ledger/src/ledger.ts +15 -0
- package/examples/icrc-ledger/src/main.tsx +10 -0
- package/examples/icrc-ledger/src/minter.ts +16 -0
- package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
- package/examples/icrc-ledger/src/sandbox.ts +281 -0
- package/examples/icrc-ledger/src/styles.css +391 -0
- package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
- package/examples/icrc-ledger/src/transfer-form.ts +97 -0
- package/examples/icrc-ledger/tsconfig.json +19 -0
- package/examples/icrc-ledger/vite.config.ts +44 -0
- package/examples/icrc-ledger/vitest.config.ts +12 -0
- package/examples/next-ssr/.stackblitzrc +4 -0
- package/examples/next-ssr/README.md +112 -0
- package/examples/next-ssr/icrc1.did +88 -0
- package/examples/next-ssr/next.config.ts +10 -0
- package/examples/next-ssr/package.json +41 -0
- package/examples/next-ssr/scripts/smoke.ts +169 -0
- package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
- package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
- package/examples/next-ssr/src/app/account/page.tsx +82 -0
- package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
- package/examples/next-ssr/src/app/globals.css +278 -0
- package/examples/next-ssr/src/app/layout.tsx +34 -0
- package/examples/next-ssr/src/app/page.tsx +46 -0
- package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
- package/examples/next-ssr/src/app/providers.test.tsx +73 -0
- package/examples/next-ssr/src/app/providers.tsx +60 -0
- package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
- package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
- package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
- package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
- package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
- package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
- package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
- package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
- package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
- package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
- package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
- package/examples/next-ssr/src/components/server-data.ts +10 -0
- package/examples/next-ssr/src/format.ts +46 -0
- package/examples/next-ssr/src/ledgers.ts +38 -0
- package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
- package/examples/next-ssr/src/server/balance-route.ts +157 -0
- package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
- package/examples/next-ssr/src/server/error-summary.ts +37 -0
- package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
- package/examples/next-ssr/src/server/parse-owner.ts +34 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
- package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
- package/examples/next-ssr/src/server/read-balances.ts +78 -0
- package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
- package/examples/next-ssr/src/server/request-client.ts +24 -0
- package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
- package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
- package/examples/next-ssr/src/testing/setup.ts +10 -0
- package/examples/next-ssr/tsconfig.json +31 -0
- package/examples/next-ssr/vitest.config.ts +59 -0
- package/examples/node-agent-tool/.stackblitzrc +4 -0
- package/examples/node-agent-tool/README.md +165 -0
- package/examples/node-agent-tool/icp.yaml +11 -0
- package/examples/node-agent-tool/icrc1.did +88 -0
- package/examples/node-agent-tool/package.json +32 -0
- package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
- package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
- package/examples/node-agent-tool/src/cli.test.ts +64 -0
- package/examples/node-agent-tool/src/cli.ts +23 -0
- package/examples/node-agent-tool/src/command-line.ts +159 -0
- package/examples/node-agent-tool/src/commands/balance.ts +73 -0
- package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
- package/examples/node-agent-tool/src/commands/demo.ts +359 -0
- package/examples/node-agent-tool/src/commands/info.ts +69 -0
- package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
- package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
- package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
- package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
- package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
- package/examples/node-agent-tool/src/commands/watch.ts +135 -0
- package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
- package/examples/node-agent-tool/src/context.ts +20 -0
- package/examples/node-agent-tool/src/dispatch.ts +70 -0
- package/examples/node-agent-tool/src/failure.test.ts +59 -0
- package/examples/node-agent-tool/src/failure.ts +176 -0
- package/examples/node-agent-tool/src/identity.test.ts +242 -0
- package/examples/node-agent-tool/src/identity.ts +228 -0
- package/examples/node-agent-tool/src/input.ts +110 -0
- package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
- package/examples/node-agent-tool/src/ledgers.ts +62 -0
- package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
- package/examples/node-agent-tool/src/network.test.ts +128 -0
- package/examples/node-agent-tool/src/network.ts +101 -0
- package/examples/node-agent-tool/src/output.test.ts +208 -0
- package/examples/node-agent-tool/src/output.ts +163 -0
- package/examples/node-agent-tool/src/run.ts +73 -0
- package/examples/node-agent-tool/src/test-kit.ts +167 -0
- package/examples/node-agent-tool/tsconfig.json +18 -0
- package/examples/node-agent-tool/vitest.config.ts +8 -0
- package/examples/vite-wallet/.stackblitzrc +4 -0
- package/examples/vite-wallet/Cargo.lock +646 -0
- package/examples/vite-wallet/Cargo.toml +5 -0
- package/examples/vite-wallet/README.md +107 -0
- package/examples/vite-wallet/backend/Cargo.toml +12 -0
- package/examples/vite-wallet/backend/backend.did +37 -0
- package/examples/vite-wallet/backend/src/lib.rs +133 -0
- package/examples/vite-wallet/icp.yaml +44 -0
- package/examples/vite-wallet/index.html +13 -0
- package/examples/vite-wallet/ledger.did +54 -0
- package/examples/vite-wallet/package.json +46 -0
- package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
- package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
- package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
- package/examples/vite-wallet/scripts/faucet.ts +41 -0
- package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
- package/examples/vite-wallet/src/AddressBook.tsx +147 -0
- package/examples/vite-wallet/src/App.tsx +53 -0
- package/examples/vite-wallet/src/Balance.test.tsx +57 -0
- package/examples/vite-wallet/src/Balance.tsx +58 -0
- package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
- package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
- package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
- package/examples/vite-wallet/src/Profile.test.tsx +145 -0
- package/examples/vite-wallet/src/Profile.tsx +92 -0
- package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
- package/examples/vite-wallet/src/SendIcp.tsx +244 -0
- package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
- package/examples/vite-wallet/src/SendToContact.tsx +102 -0
- package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
- package/examples/vite-wallet/src/SignIn.tsx +120 -0
- package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
- package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
- package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
- package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
- package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
- package/examples/vite-wallet/src/auth/local-page.ts +20 -0
- package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
- package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
- package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
- package/examples/vite-wallet/src/canisters/backend.ts +58 -0
- package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
- package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
- package/examples/vite-wallet/src/client.ts +45 -0
- package/examples/vite-wallet/src/main.tsx +18 -0
- package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
- package/examples/vite-wallet/src/refusal.ts +17 -0
- package/examples/vite-wallet/src/styles.css +282 -0
- package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
- package/examples/vite-wallet/src/token.ts +50 -0
- package/examples/vite-wallet/src/transfer.test.ts +178 -0
- package/examples/vite-wallet/src/transfer.ts +110 -0
- package/examples/vite-wallet/src/use-canisters.ts +49 -0
- package/examples/vite-wallet/tsconfig.json +19 -0
- package/examples/vite-wallet/vite.config.ts +47 -0
- package/examples/vite-wallet/vitest.config.ts +17 -0
- package/package.json +9 -4
- package/packages/core/.size-limit.js +47 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +84 -0
- package/packages/core/llms.txt +610 -0
- package/packages/core/package.json +89 -0
- package/packages/core/src/call.ts +961 -0
- package/packages/core/src/canister.ts +259 -0
- package/packages/core/src/client.ts +2061 -0
- package/packages/core/src/errors.ts +1079 -0
- package/packages/core/src/hydration.ts +239 -0
- package/packages/core/src/index.ts +23 -0
- package/packages/core/src/keys.ts +108 -0
- package/packages/core/src/management.ts +138 -0
- package/packages/core/src/network.ts +1020 -0
- package/packages/core/src/options.ts +1000 -0
- package/packages/core/src/runtime.ts +50 -0
- package/packages/core/src/testing/fake-replica.ts +1166 -0
- package/packages/core/src/testing/index.ts +16 -0
- package/packages/core/src/testing/test-auth.ts +340 -0
- package/packages/core/src/testing/test-client.ts +559 -0
- package/packages/core/src/types.ts +541 -0
- package/packages/core/src/units.ts +299 -0
- package/packages/core/src/version.ts +4 -0
- package/packages/core/tests/canister-helpers.ts +187 -0
- package/packages/core/tests/canister-id.test.ts +158 -0
- package/packages/core/tests/canister-unwrap.test.ts +94 -0
- package/packages/core/tests/canister.test-d.ts +406 -0
- package/packages/core/tests/canister.test.ts +1921 -0
- package/packages/core/tests/client-account-change.test.ts +131 -0
- package/packages/core/tests/client-agents.test.ts +402 -0
- package/packages/core/tests/client-auth-network.test.ts +719 -0
- package/packages/core/tests/client-disposed.test.ts +403 -0
- package/packages/core/tests/client-helpers.ts +136 -0
- package/packages/core/tests/client-hydration.test.ts +166 -0
- package/packages/core/tests/client-pinned.test.ts +626 -0
- package/packages/core/tests/client-session.test.ts +348 -0
- package/packages/core/tests/client.test-d.ts +181 -0
- package/packages/core/tests/client.test.ts +1162 -0
- package/packages/core/tests/encode.test.ts +171 -0
- package/packages/core/tests/env-canister-id.test.ts +666 -0
- package/packages/core/tests/env-cookie-copies.test.ts +182 -0
- package/packages/core/tests/error-guard.test.ts +222 -0
- package/packages/core/tests/errors.test-d.ts +280 -0
- package/packages/core/tests/errors.test.ts +1359 -0
- package/packages/core/tests/fixtures/README.md +28 -0
- package/packages/core/tests/fixtures/accounts.did +21 -0
- package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
- package/packages/core/tests/fixtures/accounts.ts +26 -0
- package/packages/core/tests/fixtures/archive.did +31 -0
- package/packages/core/tests/fixtures/archive.envelope.json +259 -0
- package/packages/core/tests/fixtures/archive.ts +55 -0
- package/packages/core/tests/fixtures/icrc1.did +88 -0
- package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
- package/packages/core/tests/fixtures/icrc1.ts +78 -0
- package/packages/core/tests/fixtures/management.did +724 -0
- package/packages/core/tests/fixtures/management.envelope.json +4455 -0
- package/packages/core/tests/fixtures/management.ts +486 -0
- package/packages/core/tests/fixtures/shapes.did +33 -0
- package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
- package/packages/core/tests/fixtures/shapes.ts +56 -0
- package/packages/core/tests/fixtures/skippable.did +15 -0
- package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
- package/packages/core/tests/fixtures/skippable.ts +33 -0
- package/packages/core/tests/func.test.ts +154 -0
- package/packages/core/tests/identity-attributes.test.ts +214 -0
- package/packages/core/tests/keys.test.ts +289 -0
- package/packages/core/tests/management.test.ts +206 -0
- package/packages/core/tests/network-helpers.ts +121 -0
- package/packages/core/tests/network.test-d.ts +78 -0
- package/packages/core/tests/network.test.ts +1051 -0
- package/packages/core/tests/options.test.ts +1594 -0
- package/packages/core/tests/redirect-sign-in.test.ts +393 -0
- package/packages/core/tests/resend.test-d.ts +81 -0
- package/packages/core/tests/resend.test.ts +723 -0
- package/packages/core/tests/skip.test-d.ts +125 -0
- package/packages/core/tests/testing-entry.test.ts +73 -0
- package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
- package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
- package/packages/core/tests/testing-test-auth.test.ts +483 -0
- package/packages/core/tests/testing-test-client.test-d.ts +184 -0
- package/packages/core/tests/testing-test-client.test.ts +1272 -0
- package/packages/core/tests/time-realm.test.ts +63 -0
- package/packages/core/tests/time.test.ts +238 -0
- package/packages/core/tests/traps/README.md +39 -0
- package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
- package/packages/core/tests/traps/classification-table.test.ts +256 -0
- package/packages/core/tests/traps/err-arm.test.ts +144 -0
- package/packages/core/tests/traps/fetch-spy.ts +39 -0
- package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
- package/packages/core/tests/traps/ledger.ts +248 -0
- package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
- package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
- package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
- package/packages/core/tests/traps/update-as-query.test.ts +244 -0
- package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
- package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
- package/packages/core/tests/traps.test-d.ts +476 -0
- package/packages/core/tests/units.test-d.ts +118 -0
- package/packages/core/tests/units.test.ts +634 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/core/tsconfig.typecheck.json +16 -0
- package/packages/core/vitest.config.ts +13 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +189 -0
- package/packages/mcp/package.json +62 -0
- package/packages/mcp/src/candid/candid-text.test.ts +455 -0
- package/packages/mcp/src/candid/candid-text.ts +501 -0
- package/packages/mcp/src/candid/contract.test.ts +398 -0
- package/packages/mcp/src/candid/formats.ts +155 -0
- package/packages/mcp/src/candid/hex.ts +29 -0
- package/packages/mcp/src/candid/json-schema.test.ts +758 -0
- package/packages/mcp/src/candid/json-schema.ts +454 -0
- package/packages/mcp/src/candid/json.test.ts +1207 -0
- package/packages/mcp/src/candid/json.ts +1053 -0
- package/packages/mcp/src/candid/schema-walk.ts +189 -0
- package/packages/mcp/src/candid/type-text.test.ts +179 -0
- package/packages/mcp/src/candid/type-text.ts +238 -0
- package/packages/mcp/src/cli.ts +134 -0
- package/packages/mcp/src/identity.ts +188 -0
- package/packages/mcp/src/interface/cache.test.ts +48 -0
- package/packages/mcp/src/interface/cache.ts +68 -0
- package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
- package/packages/mcp/src/interface/cli-cache.ts +122 -0
- package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
- package/packages/mcp/src/interface/compile-worker.ts +290 -0
- package/packages/mcp/src/interface/compile.test.ts +155 -0
- package/packages/mcp/src/interface/compile.ts +281 -0
- package/packages/mcp/src/interface/docs.test.ts +105 -0
- package/packages/mcp/src/interface/docs.ts +86 -0
- package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
- package/packages/mcp/src/interface/hex.ts +8 -0
- package/packages/mcp/src/interface/ids.test.ts +71 -0
- package/packages/mcp/src/interface/ids.ts +87 -0
- package/packages/mcp/src/interface/loader.test.ts +804 -0
- package/packages/mcp/src/interface/loader.ts +519 -0
- package/packages/mcp/src/interface/read-candid.test.ts +300 -0
- package/packages/mcp/src/interface/read-candid.ts +259 -0
- package/packages/mcp/src/interface/types.ts +113 -0
- package/packages/mcp/src/network.ts +122 -0
- package/packages/mcp/src/options.ts +149 -0
- package/packages/mcp/src/server/approval.ts +159 -0
- package/packages/mcp/src/server/naming.ts +43 -0
- package/packages/mcp/src/server/results.ts +102 -0
- package/packages/mcp/src/server/server.ts +797 -0
- package/packages/mcp/src/server/tools.ts +116 -0
- package/packages/mcp/src/usage.ts +40 -0
- package/packages/mcp/tests/approval.test.ts +60 -0
- package/packages/mcp/tests/identity.test.ts +234 -0
- package/packages/mcp/tests/naming.test.ts +62 -0
- package/packages/mcp/tests/options.test.ts +140 -0
- package/packages/mcp/tests/server.test.ts +997 -0
- package/packages/mcp/tsconfig.json +19 -0
- package/packages/mcp/tsconfig.typecheck.json +10 -0
- package/packages/mcp/vitest.config.ts +10 -0
- package/packages/react/LICENSE +21 -0
- package/packages/react/README.md +328 -0
- package/packages/react/llms.txt +95 -0
- package/packages/react/package.json +95 -0
- package/packages/react/src/index.tsx +678 -0
- package/packages/react/tests/abandoned-render.test.tsx +816 -0
- package/packages/react/tests/auth.test.tsx +315 -0
- package/packages/react/tests/caller-keys.test.tsx +2334 -0
- package/packages/react/tests/client-time.test.ts +40 -0
- package/packages/react/tests/entry.test.ts +52 -0
- package/packages/react/tests/helpers.tsx +291 -0
- package/packages/react/tests/hydration.test.tsx +262 -0
- package/packages/react/tests/ownership.test.tsx +260 -0
- package/packages/react/tests/provider.server.test.tsx +169 -0
- package/packages/react/tests/query-client.test.tsx +107 -0
- package/packages/react/tests/strict-mode.test.tsx +288 -0
- package/packages/react/tests/suspense.test-d.tsx +357 -0
- package/packages/react/tests/two-query-cores.test.tsx +135 -0
- package/packages/react/tests/types.test-d.tsx +63 -0
- package/packages/react/tsconfig.json +23 -0
- package/packages/react/tsconfig.test.json +8 -0
- package/packages/react/tsconfig.typecheck.json +16 -0
- package/packages/react/vitest.config.ts +39 -0
- package/packages/vite-plugin/LICENSE +21 -0
- package/packages/vite-plugin/README.md +532 -0
- package/packages/vite-plugin/package.json +76 -0
- package/packages/vite-plugin/src/describe-value.test.ts +43 -0
- package/packages/vite-plugin/src/describe-value.ts +167 -0
- package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
- package/packages/vite-plugin/src/dev-environment.ts +510 -0
- package/packages/vite-plugin/src/env.ts +431 -0
- package/packages/vite-plugin/src/fetch.ts +348 -0
- package/packages/vite-plugin/src/file-identity.ts +106 -0
- package/packages/vite-plugin/src/generate.ts +691 -0
- package/packages/vite-plugin/src/index.test.ts +1243 -0
- package/packages/vite-plugin/src/index.ts +2565 -0
- package/packages/vite-plugin/src/interface.ts +339 -0
- package/packages/vite-plugin/src/local-host.ts +34 -0
- package/packages/vite-plugin/src/options.ts +641 -0
- package/packages/vite-plugin/src/principal-text.test.ts +87 -0
- package/packages/vite-plugin/src/principal-text.ts +98 -0
- package/packages/vite-plugin/tests/close.test.ts +141 -0
- package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
- package/packages/vite-plugin/tests/fake-ic.ts +214 -0
- package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
- package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
- package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
- package/packages/vite-plugin/tests/generation.test.ts +1036 -0
- package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
- package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
- package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
- package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
- package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
- package/packages/vite-plugin/tests/package.test.ts +62 -0
- package/packages/vite-plugin/tests/project.test.ts +2993 -0
- package/packages/vite-plugin/tests/support.ts +339 -0
- package/packages/vite-plugin/tests/timeout.test.ts +107 -0
- package/packages/vite-plugin/tsconfig.json +23 -0
- package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
- package/packages/vite-plugin/vitest.config.ts +9 -0
- package/pnpm-workspace.yaml +23 -0
- package/scripts/ai-context-files.js +72 -0
- package/scripts/assemble-docs-site.mjs +485 -0
- package/scripts/assemble-docs-site.test.mjs +568 -0
- package/scripts/build-examples.js +58 -0
- package/scripts/check-ai-context.js +963 -0
- package/scripts/check-ai-context.test.mjs +811 -0
- package/scripts/check-exports.mjs +767 -0
- package/scripts/check-exports.test.mjs +1136 -0
- package/scripts/check-snippets/ambient.d.ts +428 -0
- package/scripts/check-snippets/app/generated/backend.did +7 -0
- package/scripts/check-snippets/app/generated/backend.ts +15 -0
- package/scripts/check-snippets/app/generated/icrc1.did +88 -0
- package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
- package/scripts/check-snippets/app/globals.ts +26 -0
- package/scripts/check-snippets/docs/guides/App.tsx +21 -0
- package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
- package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
- package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
- package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
- package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
- package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
- package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
- package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
- package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
- package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
- package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
- package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
- package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
- package/scripts/check-snippets.mjs +993 -0
- package/scripts/check-snippets.test.mjs +338 -0
- package/scripts/check-stack.mjs +501 -0
- package/scripts/check-stack.test.mjs +800 -0
- package/scripts/clean-node-modules.js +67 -0
- package/scripts/example-projects.js +58 -0
- package/scripts/export-budget.mjs +145 -0
- package/scripts/faults.json +6749 -0
- package/scripts/lib/fault-workspace.mjs +210 -0
- package/scripts/lib/io.mjs +29 -0
- package/scripts/lib/io.test.mjs +38 -0
- package/scripts/lib/markdown-fences.mjs +56 -0
- package/scripts/lib/markdown-fences.test.mjs +84 -0
- package/scripts/lib/package-exports.mjs +54 -0
- package/scripts/lib/package-exports.test.mjs +66 -0
- package/scripts/lib/release-pr-steps.mjs +47 -0
- package/scripts/release-tag.mjs +95 -0
- package/scripts/release-tag.test.mjs +278 -0
- package/scripts/release-workflow.test.mjs +98 -0
- package/scripts/release.js +167 -0
- package/scripts/release.test.mjs +347 -0
- package/scripts/removed-v3-names.js +125 -0
- package/scripts/size-app/.size-limit.js +90 -0
- package/scripts/size-app/core.js +3 -0
- package/scripts/size-app/package.json +24 -0
- package/scripts/size-app/react.js +4 -0
- package/scripts/size-app.test.mjs +73 -0
- package/scripts/sync-ai-context-versions.js +102 -0
- package/scripts/sync-example-versions.js +151 -0
- package/scripts/test-examples-published.mjs +1585 -0
- package/scripts/test-examples-published.test.mjs +1586 -0
- package/scripts/traps/any-method-vars-not-a-list.json +5 -0
- package/scripts/traps/canister-method-unknown.json +5 -0
- package/scripts/traps/client-says-who-calls-once.json +5 -0
- package/scripts/traps/client-says-who-calls.json +5 -0
- package/scripts/traps/err-only-on-canister-err.json +14 -0
- package/scripts/traps/format-units-refuses-missing-value.json +5 -0
- package/scripts/traps/format-units-refuses-number.json +5 -0
- package/scripts/traps/format-units-unknown-option.json +5 -0
- package/scripts/traps/guard-err-is-the-methods.json +5 -0
- package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
- package/scripts/traps/internals-not-exported.json +9 -0
- package/scripts/traps/network-refuses-unknown-name.json +5 -0
- package/scripts/traps/never-err-has-no-type.json +5 -0
- package/scripts/traps/options-method-unknown.json +5 -0
- package/scripts/traps/options-vars-wrong-type.json +5 -0
- package/scripts/traps/parse-units-refuses-number.json +5 -0
- package/scripts/traps/parse-units-unknown-option.json +5 -0
- package/scripts/traps/principal-is-not-plain-text.json +14 -0
- package/scripts/traps/query-options-refuses-oneway.json +5 -0
- package/scripts/traps/query-options-refuses-update.json +5 -0
- package/scripts/traps/refuse-next-unknown-option.json +5 -0
- package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
- package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
- package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
- package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
- package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
- package/scripts/traps/test-handler-method-unknown.json +5 -0
- package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
- package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
- package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
- package/scripts/typecheck-examples.js +122 -0
- package/scripts/verify-faults.mjs +375 -0
- package/scripts/verify-faults.test.mjs +402 -0
- package/scripts/verify-package-artifacts.mjs +590 -0
- package/scripts/verify-peer-floors.js +399 -0
- package/scripts/verify-peer-floors.test.mjs +422 -0
- package/scripts/verify-test-fails-rules.mjs +104 -0
- package/scripts/verify-test-fails-rules.test.mjs +215 -0
- package/scripts/verify-test-fails.js +371 -0
- package/scripts/verify-traps.mjs +505 -0
- package/scripts/verify-traps.test.mjs +521 -0
- package/scripts/verify-typescript-floor.js +239 -0
- package/skill-packages/ic-reactor/SKILL.md +103 -0
- package/stack.json +8 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One error type for every failure an ic-reactor call reports, and the
|
|
3
|
+
* classifier that turns what `@icp-sdk/core` throws into it.
|
|
4
|
+
*
|
|
5
|
+
* The question a UI has to answer after a failed write is not "what went
|
|
6
|
+
* wrong" but "did it happen anyway". Every {@link ReactorError} therefore
|
|
7
|
+
* carries `mayHaveExecuted`, derived here from the failure and the call mode
|
|
8
|
+
* so that no application has to read reject codes or HTTP statuses itself.
|
|
9
|
+
*
|
|
10
|
+
* Only {@link ReactorError}, {@link ReactorErrorKind} and
|
|
11
|
+
* {@link isReactorError} are part of the package entry. Everything else in
|
|
12
|
+
* this module is internal and exported only for the client that builds on it.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { MANAGEMENT_CANISTER } from "./management.js"
|
|
17
|
+
import { isServer } from "./runtime.js"
|
|
18
|
+
import type { Canister, ErrorOf } from "./types.js"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What kind of failure a {@link ReactorError} reports.
|
|
22
|
+
*
|
|
23
|
+
* - `invalid_args`: the arguments could not be encoded, or the target canister
|
|
24
|
+
* id could not be resolved. Nothing was sent.
|
|
25
|
+
* - `unauthenticated`: an update was attempted while not signed in. Nothing was
|
|
26
|
+
* sent.
|
|
27
|
+
* - `not_delivered`: nothing that matters ran. Either the request was refused
|
|
28
|
+
* or never taken in (the replica or a boundary node turned it away, or the
|
|
29
|
+
* system could not take it in), or it was a query, which changes nothing, and
|
|
30
|
+
* no answer came back. A failed query may well have reached the canister,
|
|
31
|
+
* so `mayHaveExecuted` carries the guarantee: it is `false` on every
|
|
32
|
+
* `not_delivered`, because a refused update never ran and a query changes
|
|
33
|
+
* nothing.
|
|
34
|
+
* - `outcome_unknown`: an update went out and no trustworthy answer came back,
|
|
35
|
+
* so the canister may or may not have run it. So does a re-send of a write
|
|
36
|
+
* `client.resendOf()` offered that did not run, refused before it went out
|
|
37
|
+
* or turned away: the write it re-sends may have run all the same. Its
|
|
38
|
+
* `code`, `httpStatus` and `rejectCode` say why the re-send did not run
|
|
39
|
+
* (such as `caller_changed`, `target_changed` or `arg_changed`), and its
|
|
40
|
+
* `cause` is the re-send's own error.
|
|
41
|
+
* - `rejected`: the IC or the canister rejected the call; `rejectCode` says which.
|
|
42
|
+
* - `invalid_reply`: a reply arrived but did not decode as the method's result.
|
|
43
|
+
* - `canister_err`: the canister ran the call and replied with the `Err` arm of
|
|
44
|
+
* its result; `err` is typed. The outcome is that answer, so
|
|
45
|
+
* `mayHaveExecuted` is `false`: re-sending is a new decision, not a retry.
|
|
46
|
+
* - `cancelled`: the caller abandoned the call (an aborted query, a key whose
|
|
47
|
+
* principal is no longer current, a call on a disposed client
|
|
48
|
+
* (`client_disposed`), a read or an unsent write whose `timeoutMs` passed
|
|
49
|
+
* (`timeout`), or an update aborted in flight). A write whose `timeoutMs` passed after its request went out
|
|
50
|
+
* is `outcome_unknown` (`timeout`) instead.
|
|
51
|
+
* `mayHaveExecuted` is `false` when nothing was sent, and `true` for an
|
|
52
|
+
* update aborted after its request may already have been delivered.
|
|
53
|
+
*/
|
|
54
|
+
export type ReactorErrorKind =
|
|
55
|
+
| "invalid_args"
|
|
56
|
+
| "unauthenticated"
|
|
57
|
+
| "not_delivered"
|
|
58
|
+
| "outcome_unknown"
|
|
59
|
+
| "rejected"
|
|
60
|
+
| "invalid_reply"
|
|
61
|
+
| "canister_err"
|
|
62
|
+
| "cancelled"
|
|
63
|
+
|
|
64
|
+
type ReactorErrorBase = Error & {
|
|
65
|
+
readonly name: "ReactorError"
|
|
66
|
+
/**
|
|
67
|
+
* Whether the call may have taken effect although the caller cannot tell
|
|
68
|
+
* what it did. `true` is a warning, not a verdict: the IC or the network
|
|
69
|
+
* left the outcome open, so re-sending could run the call twice; read the
|
|
70
|
+
* state back before trying again. `false` means the outcome is known: the
|
|
71
|
+
* call was refused before it ran, or, for `canister_err`, the canister ran
|
|
72
|
+
* it and answered with its decision in `err` (a mutation still invalidates
|
|
73
|
+
* its reads after one, since a canister may change state before returning
|
|
74
|
+
* an `Err`).
|
|
75
|
+
*/
|
|
76
|
+
readonly mayHaveExecuted: boolean
|
|
77
|
+
/** The Candid method that was called. */
|
|
78
|
+
readonly method: string
|
|
79
|
+
/** The target canister, as text. */
|
|
80
|
+
readonly canisterId: string
|
|
81
|
+
/**
|
|
82
|
+
* A machine-readable sub-reason within a `kind`, such as
|
|
83
|
+
* `"canister_id_unresolved"` for an unresolved `{ name }` target,
|
|
84
|
+
* `"anonymous_write"` for an update without a signed-in identity, or
|
|
85
|
+
* `"canister_not_found"` when the IC answered that the canister the request
|
|
86
|
+
* was routed to does not exist (for a call to `aaaaa-aa`, the canister its
|
|
87
|
+
* arguments name, while `canisterId` stays `aaaaa-aa`).
|
|
88
|
+
*/
|
|
89
|
+
readonly code?: string
|
|
90
|
+
/** The IC reject code (1 to 6, or whatever the replica sent), when there was one. */
|
|
91
|
+
readonly rejectCode?: number
|
|
92
|
+
/** The HTTP status the replica or a boundary node answered with, when there was one. */
|
|
93
|
+
readonly httpStatus?: number
|
|
94
|
+
/** Which parts of the arguments or the reply failed to encode or decode. */
|
|
95
|
+
readonly issues?: readonly { code: string; path: string; message: string }[]
|
|
96
|
+
/** The error that caused this one, when there is one. */
|
|
97
|
+
readonly cause?: unknown
|
|
98
|
+
/**
|
|
99
|
+
* For a write that got no answer (`outcome_unknown`, or `cancelled` with
|
|
100
|
+
* `mayHaveExecuted: true`): the latest ingress expiry its sends were signed
|
|
101
|
+
* with, in nanoseconds since the epoch, on the IC's clock. Once the IC's
|
|
102
|
+
* time is past it, no send of this write can still start executing (the IC
|
|
103
|
+
* interface specification: the IC neither accepts a call nor moves it from
|
|
104
|
+
* `received` to `processing` past its `ingress_expiry`:
|
|
105
|
+
* https://docs.internetcomputer.org/references/ic-interface-spec/https-interface/).
|
|
106
|
+
* The expiry does not stop a send that started before it, so only for a
|
|
107
|
+
* method that commits without awaiting another canister is a certified
|
|
108
|
+
* read-back of later state that finds nothing final. The host's clock may
|
|
109
|
+
* differ from the IC's: compare with a margin. Absent on every other error.
|
|
110
|
+
* Not enumerable, so `JSON.stringify(error)` still works.
|
|
111
|
+
*/
|
|
112
|
+
readonly expiresAt?: bigint
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The error every ic-reactor call rejects with.
|
|
117
|
+
*
|
|
118
|
+
* Narrow on `kind`. For a method whose result has an `Err` arm, `E` is that
|
|
119
|
+
* arm's type, and `err` is typed `E` exactly when `kind` is `"canister_err"`
|
|
120
|
+
* (and `undefined` for every other kind). `E` defaults to `never`, which
|
|
121
|
+
* stands for a method without an `Err` arm: `err` is then always `undefined`.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* try {
|
|
126
|
+
* await ledger.icrc1_transfer(args)
|
|
127
|
+
* } catch (e) {
|
|
128
|
+
* if (isReactorError(e) && e.mayHaveExecuted) {
|
|
129
|
+
* // Do not re-send: read the balance back first.
|
|
130
|
+
* }
|
|
131
|
+
* }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export type ReactorError<E = never> = [E] extends [never]
|
|
135
|
+
? ReactorErrorBase & {
|
|
136
|
+
readonly kind: ReactorErrorKind
|
|
137
|
+
readonly err?: undefined
|
|
138
|
+
}
|
|
139
|
+
: ReactorErrorBase &
|
|
140
|
+
(
|
|
141
|
+
| {
|
|
142
|
+
readonly kind: Exclude<ReactorErrorKind, "canister_err">
|
|
143
|
+
readonly err?: undefined
|
|
144
|
+
}
|
|
145
|
+
| { readonly kind: "canister_err"; readonly err: E }
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Brand on every error this module creates. `Symbol.for` returns the same
|
|
150
|
+
* symbol in every copy of the package, so a guard from one copy recognises an
|
|
151
|
+
* error thrown by another (a version range the package manager cannot dedupe,
|
|
152
|
+
* or a bundler that pre-bundles one importer and not the other), which
|
|
153
|
+
* `instanceof` cannot do.
|
|
154
|
+
*/
|
|
155
|
+
const BRAND = Symbol.for("ic-reactor.ReactorError")
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Marks an error as one that is safe to send again. Internal: it is not part
|
|
159
|
+
* of {@link ReactorError}. Shared across copies for the same reason as
|
|
160
|
+
* {@link BRAND}.
|
|
161
|
+
*/
|
|
162
|
+
const RETRYABLE = Symbol.for("ic-reactor.ReactorError.retryable")
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The canister object a call was made on, on every error a call made through
|
|
166
|
+
* one rejects with (see {@link fromCanister}). Internal: it is not part of
|
|
167
|
+
* {@link ReactorError}. Shared across copies for the same reason as
|
|
168
|
+
* {@link BRAND}.
|
|
169
|
+
*/
|
|
170
|
+
const CANISTER = Symbol.for("ic-reactor.ReactorError.canister")
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether `error` is a {@link ReactorError} that a call of `method` on
|
|
174
|
+
* `canister` rejected with. Narrows to `ReactorError<E>`, where `E` is the
|
|
175
|
+
* method's `Err` arm, and whose `method` is `method`: after a check on
|
|
176
|
+
* `kind === "canister_err"`, `err` is typed. For a method without an `Err`
|
|
177
|
+
* arm, `err` is `undefined`. When it is `false`, the error is only not that
|
|
178
|
+
* call's: it keeps every `kind`.
|
|
179
|
+
*
|
|
180
|
+
* The check is made at run time, not only in the types: it is `false` for an
|
|
181
|
+
* error from another method, from another canister object (the one made with
|
|
182
|
+
* `certified: true` is another object, and so is a canister of another
|
|
183
|
+
* client), and from a func reference, so it never narrows an error to the
|
|
184
|
+
* wrong `Err` type. `client.canister()` returns the same object for the same
|
|
185
|
+
* generated module and target, so the canister can be made again where the
|
|
186
|
+
* error is caught.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* try {
|
|
191
|
+
* await ledger.icrc1_transfer(arg)
|
|
192
|
+
* } catch (error) {
|
|
193
|
+
* if (isReactorError(error, ledger, "icrc1_transfer") && error.kind === "canister_err") {
|
|
194
|
+
* error.err // the ledger's TransferError
|
|
195
|
+
* }
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function isReactorError<A, M extends keyof A & string>(
|
|
200
|
+
error: unknown,
|
|
201
|
+
canister: Canister<A>,
|
|
202
|
+
method: M
|
|
203
|
+
): error is ReactorError<ErrorOf<A, M>> & { readonly method: M }
|
|
204
|
+
/**
|
|
205
|
+
* Whether `error` is a {@link ReactorError}, including one created by another
|
|
206
|
+
* copy of this package. Narrows to `ReactorError<unknown>`: after a check on
|
|
207
|
+
* `kind === "canister_err"`, `err` is `unknown`. It can be passed point-free,
|
|
208
|
+
* as in `errors.filter(isReactorError)`: the index and the array an array
|
|
209
|
+
* method passes are not a canister and a method. This signature is listed
|
|
210
|
+
* after the three-argument one so that point-free use infers it.
|
|
211
|
+
*/
|
|
212
|
+
export function isReactorError(error: unknown): error is ReactorError<unknown>
|
|
213
|
+
export function isReactorError(
|
|
214
|
+
error: unknown,
|
|
215
|
+
canister?: unknown,
|
|
216
|
+
method?: unknown
|
|
217
|
+
): boolean {
|
|
218
|
+
const own = error as Record<PropertyKey, unknown>
|
|
219
|
+
return (
|
|
220
|
+
typeof error === "object" &&
|
|
221
|
+
error !== null &&
|
|
222
|
+
own[BRAND] === true &&
|
|
223
|
+
// With a canister object or a method name, the error must say it came
|
|
224
|
+
// from exactly that call, and either one alone is never enough. Other
|
|
225
|
+
// extra arguments are not this form's: passed point-free, as in
|
|
226
|
+
// `errors.filter(isReactorError)`, the guard is called with an index and
|
|
227
|
+
// an array, and stays the plain guard.
|
|
228
|
+
(typeof canister === "object" || typeof method === "string"
|
|
229
|
+
? !!canister && own[CANISTER] === canister && own.method === method
|
|
230
|
+
: true)
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Marks `error`, when it is a {@link ReactorError}, as one a call on
|
|
236
|
+
* `canister` rejected with, for the guard's three-argument form, and returns
|
|
237
|
+
* it. Without a canister (a func reference) nothing is marked. An error
|
|
238
|
+
* already marked keeps its first canister, as it keeps its `method` and
|
|
239
|
+
* `canisterId`.
|
|
240
|
+
*/
|
|
241
|
+
export const fromCanister = (error: unknown, canister?: object): unknown => {
|
|
242
|
+
if (canister && isReactorError(error) && !(CANISTER in error)) {
|
|
243
|
+
Object.defineProperty(error, CANISTER, { value: canister })
|
|
244
|
+
}
|
|
245
|
+
return error
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Creating errors
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
type Issues = NonNullable<ReactorErrorBase["issues"]>
|
|
253
|
+
|
|
254
|
+
/** Whether a call reads (`query`) or writes (`update`). */
|
|
255
|
+
export type CallMode = "query" | "update"
|
|
256
|
+
|
|
257
|
+
/** Fields shared by every error the client creates itself. */
|
|
258
|
+
export interface ReactorErrorFields {
|
|
259
|
+
/** The Candid method that was called. */
|
|
260
|
+
readonly method: string
|
|
261
|
+
/** The target canister as text, or the `$unresolved:<name>` placeholder. */
|
|
262
|
+
readonly canisterId: string
|
|
263
|
+
/**
|
|
264
|
+
* A short, readable reason. It is appended to
|
|
265
|
+
* `[ic-reactor] <method> on <canisterId>: ` to form `message`. Each kind has
|
|
266
|
+
* a default.
|
|
267
|
+
*/
|
|
268
|
+
readonly reason?: string
|
|
269
|
+
/** A machine-readable sub-reason: `ReactorError.code`. */
|
|
270
|
+
readonly code?: string
|
|
271
|
+
readonly issues?: Issues
|
|
272
|
+
readonly cause?: unknown
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface BuildInit extends ReactorErrorFields {
|
|
276
|
+
readonly kind: ReactorErrorKind
|
|
277
|
+
readonly mayHaveExecuted: boolean
|
|
278
|
+
readonly reason: string
|
|
279
|
+
readonly rejectCode?: number
|
|
280
|
+
readonly httpStatus?: number
|
|
281
|
+
readonly err?: unknown
|
|
282
|
+
readonly retryable?: boolean
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
class ReactorFailure extends Error {
|
|
286
|
+
declare readonly kind: ReactorErrorKind
|
|
287
|
+
declare readonly mayHaveExecuted: boolean
|
|
288
|
+
declare readonly method: string
|
|
289
|
+
declare readonly canisterId: string
|
|
290
|
+
declare readonly code?: string
|
|
291
|
+
declare readonly rejectCode?: number
|
|
292
|
+
declare readonly httpStatus?: number
|
|
293
|
+
declare readonly issues?: Issues
|
|
294
|
+
declare readonly err?: unknown
|
|
295
|
+
declare readonly cause?: unknown
|
|
296
|
+
declare readonly expiresAt?: bigint
|
|
297
|
+
|
|
298
|
+
constructor(init: BuildInit) {
|
|
299
|
+
super(`[ic-reactor] ${init.method} on ${init.canisterId}: ${init.reason}`)
|
|
300
|
+
// Absent fields stay absent (no own key), so `"err" in error` and
|
|
301
|
+
// `JSON.stringify` mean what they say.
|
|
302
|
+
const own: Record<string, unknown> = {
|
|
303
|
+
kind: init.kind,
|
|
304
|
+
mayHaveExecuted: init.mayHaveExecuted,
|
|
305
|
+
method: init.method,
|
|
306
|
+
canisterId: init.canisterId,
|
|
307
|
+
}
|
|
308
|
+
for (const key of ["code", "rejectCode", "httpStatus", "issues"]) {
|
|
309
|
+
const value = init[key as keyof BuildInit]
|
|
310
|
+
if (value !== undefined) own[key] = value
|
|
311
|
+
}
|
|
312
|
+
if (init.kind === "canister_err") own.err = init.err
|
|
313
|
+
Object.assign(this, own)
|
|
314
|
+
// Like the platform's own `cause`: present, but not enumerable.
|
|
315
|
+
if (init.cause !== undefined) {
|
|
316
|
+
Object.defineProperty(this, "cause", {
|
|
317
|
+
value: init.cause,
|
|
318
|
+
writable: true,
|
|
319
|
+
configurable: true,
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
Object.defineProperty(this, BRAND, { value: true })
|
|
323
|
+
Object.defineProperty(this, RETRYABLE, { value: init.retryable === true })
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Defined on the prototype so the stack header reads `ReactorError: ...`.
|
|
328
|
+
Object.defineProperty(ReactorFailure.prototype, "name", {
|
|
329
|
+
value: "ReactorError",
|
|
330
|
+
writable: true,
|
|
331
|
+
configurable: true,
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* `error` with `expiresAt`, when it is a write's failure that got no answer
|
|
336
|
+
* (`outcome_unknown`, or `cancelled` with `mayHaveExecuted: true`) and a send
|
|
337
|
+
* of it recorded an ingress expiry. Not enumerable, as `cause` is not: a
|
|
338
|
+
* `bigint` would make `JSON.stringify(error)` throw.
|
|
339
|
+
*/
|
|
340
|
+
export const withExpiry = (
|
|
341
|
+
error: unknown,
|
|
342
|
+
expiresAt: bigint | undefined
|
|
343
|
+
): unknown => {
|
|
344
|
+
if (
|
|
345
|
+
expiresAt !== undefined &&
|
|
346
|
+
isReactorError(error) &&
|
|
347
|
+
error.mayHaveExecuted &&
|
|
348
|
+
(error.kind === "outcome_unknown" || error.kind === "cancelled")
|
|
349
|
+
) {
|
|
350
|
+
Object.defineProperty(error, "expiresAt", {
|
|
351
|
+
value: expiresAt,
|
|
352
|
+
configurable: true,
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
return error
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const build = <E = never>(init: BuildInit): ReactorError<E> =>
|
|
359
|
+
new ReactorFailure(init) as unknown as ReactorError<E>
|
|
360
|
+
|
|
361
|
+
const DEFAULT_REASON = {
|
|
362
|
+
invalid_args: "the arguments could not be encoded",
|
|
363
|
+
unauthenticated: "an update needs a signed-in identity",
|
|
364
|
+
cancelled: "the call was cancelled",
|
|
365
|
+
invalid_reply: "the reply did not decode as the method's result",
|
|
366
|
+
canister_err: "the canister returned an Err",
|
|
367
|
+
} as const
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Creates a {@link ReactorError} for a failure the client detects itself,
|
|
371
|
+
* where there is no agent error to classify.
|
|
372
|
+
*
|
|
373
|
+
* `mayHaveExecuted` is derived, never passed in: `invalid_args`,
|
|
374
|
+
* `unauthenticated`, `cancelled` and `canister_err` always say `false` (the
|
|
375
|
+
* client refuses before sending, or the canister answered with a decision),
|
|
376
|
+
* and `invalid_reply` needs the call `mode` because a write whose reply cannot
|
|
377
|
+
* be read has still run, and a read has not.
|
|
378
|
+
*
|
|
379
|
+
* Errors made here are never retryable.
|
|
380
|
+
*/
|
|
381
|
+
export function createReactorError(
|
|
382
|
+
kind: "invalid_args" | "unauthenticated" | "cancelled",
|
|
383
|
+
fields: ReactorErrorFields
|
|
384
|
+
): ReactorError
|
|
385
|
+
export function createReactorError(
|
|
386
|
+
kind: "invalid_reply",
|
|
387
|
+
fields: ReactorErrorFields & { readonly mode: CallMode }
|
|
388
|
+
): ReactorError
|
|
389
|
+
export function createReactorError<E>(
|
|
390
|
+
kind: "canister_err",
|
|
391
|
+
fields: ReactorErrorFields & { readonly err: E }
|
|
392
|
+
): ReactorError<E>
|
|
393
|
+
export function createReactorError(
|
|
394
|
+
kind: keyof typeof DEFAULT_REASON,
|
|
395
|
+
fields: ReactorErrorFields & {
|
|
396
|
+
readonly mode?: CallMode
|
|
397
|
+
readonly err?: unknown
|
|
398
|
+
}
|
|
399
|
+
): ReactorError<unknown> {
|
|
400
|
+
const { mode, err, reason, ...rest } = fields
|
|
401
|
+
return build({
|
|
402
|
+
...rest,
|
|
403
|
+
kind,
|
|
404
|
+
reason: reason ?? DEFAULT_REASON[kind],
|
|
405
|
+
mayHaveExecuted: kind === "invalid_reply" && mode === "update",
|
|
406
|
+
err,
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The error of a call whose `timeoutMs` passed (code `"timeout"`). `sent` is
|
|
412
|
+
* whether a write (an update or a oneway, or an update read with
|
|
413
|
+
* `{ update: "idempotent" }`) may have been delivered: an attempt of it was
|
|
414
|
+
* out. Then `outcome_unknown` with `mayHaveExecuted: true`, as when the
|
|
415
|
+
* polling bound passes. Otherwise `cancelled`, with nothing executed: nothing
|
|
416
|
+
* was out, or the call was a plain read. A re-send of a write
|
|
417
|
+
* `client.resendOf()` offered that times out before it goes out keeps the
|
|
418
|
+
* unknown outcome all the same ({@link resendDidNotRun}).
|
|
419
|
+
*/
|
|
420
|
+
export const timedOutError = (
|
|
421
|
+
where: { readonly method: string; readonly canisterId: string },
|
|
422
|
+
sent: boolean
|
|
423
|
+
): ReactorError =>
|
|
424
|
+
build({
|
|
425
|
+
...where,
|
|
426
|
+
kind: sent ? "outcome_unknown" : "cancelled",
|
|
427
|
+
mayHaveExecuted: sent,
|
|
428
|
+
code: "timeout",
|
|
429
|
+
reason: `timeoutMs passed${sent ? " with no answer; the write may have executed" : ""}`,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* The error of a re-send of a write `client.resendOf()` offered that did not
|
|
434
|
+
* run: refused before it went out (`caller_changed`, `target_changed`,
|
|
435
|
+
* `arg_changed`, `client_disposed`, a `timeout` before it went out, and any
|
|
436
|
+
* other refusal before sending), or turned away (an HTTP 4xx, reject code 1
|
|
437
|
+
* to 3). `failure`, what the re-send itself rejected with, proves only that
|
|
438
|
+
* the re-send did not run. The write it re-sends may have run all the same,
|
|
439
|
+
* so its outcome stays unknown: `outcome_unknown` with `mayHaveExecuted:
|
|
440
|
+
* true`, `failure`'s `method`, `canisterId`, `code`, `rejectCode`,
|
|
441
|
+
* `httpStatus` and `issues` (the call that was refused), `failure` itself as
|
|
442
|
+
* the `cause` (its message says why the re-send did not run), and
|
|
443
|
+
* `expiresAt`, the latest of the write's sends. A re-send that ran is
|
|
444
|
+
* answered by the canister (`canister_err`, such as a ledger's `Duplicate`),
|
|
445
|
+
* and one that may have run says so itself: neither comes here.
|
|
446
|
+
*/
|
|
447
|
+
export const resendDidNotRun = (
|
|
448
|
+
failure: ReactorError | ReactorError<unknown>,
|
|
449
|
+
expiresAt: bigint | undefined
|
|
450
|
+
): ReactorError =>
|
|
451
|
+
withExpiry(
|
|
452
|
+
// The spread takes `failure`'s own enumerable fields (method, canisterId,
|
|
453
|
+
// code, rejectCode, httpStatus, issues); its kind and verdict are replaced.
|
|
454
|
+
build({
|
|
455
|
+
...failure,
|
|
456
|
+
kind: "outcome_unknown",
|
|
457
|
+
mayHaveExecuted: true,
|
|
458
|
+
reason: "the re-send did not run; the write may have executed",
|
|
459
|
+
cause: failure,
|
|
460
|
+
}),
|
|
461
|
+
expiresAt
|
|
462
|
+
) as ReactorError
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Creates the `invalid_reply` error for a reply that did not decode as the
|
|
466
|
+
* method's result type: `mayHaveExecuted` is `true` on an update (the canister
|
|
467
|
+
* ran it, and only the answer is unreadable) and `false` on a query.
|
|
468
|
+
*/
|
|
469
|
+
export function invalidReplyError(
|
|
470
|
+
context: Pick<ErrorContext, "method" | "canisterId" | "mode">,
|
|
471
|
+
details: Pick<ReactorErrorFields, "reason" | "issues" | "cause"> = {}
|
|
472
|
+
): ReactorError {
|
|
473
|
+
return createReactorError("invalid_reply", {
|
|
474
|
+
method: context.method,
|
|
475
|
+
canisterId: context.canisterId,
|
|
476
|
+
mode: context.mode,
|
|
477
|
+
...details,
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
// Classifying what the agent (@icp-sdk/core 6) throws. Read structurally:
|
|
483
|
+
// `kind`, `code.name`, `code.rejectCode`, `code.status`; never `instanceof`, so
|
|
484
|
+
// an error from another copy of the agent classifies the same way.
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
interface ErrorContextBase {
|
|
488
|
+
readonly method: string
|
|
489
|
+
/** The target canister, as text. `aaaaa-aa` is the management canister. */
|
|
490
|
+
readonly canisterId: string
|
|
491
|
+
/**
|
|
492
|
+
* The signal the call ran under. If it has aborted, the caller is gone and
|
|
493
|
+
* the failure is `cancelled`, whatever the agent reported.
|
|
494
|
+
*/
|
|
495
|
+
readonly signal?: AbortSignal
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* What the classifier needs to know about the call that failed.
|
|
500
|
+
*
|
|
501
|
+
* An update must say whether the replica may already have accepted the request
|
|
502
|
+
* (`accepted`). It is required there, not defaulted, because the unsafe
|
|
503
|
+
* reading is the one a caller gets by forgetting it: an HTTP refusal that
|
|
504
|
+
* arrives after the update is in the IC would read as "never delivered, safe
|
|
505
|
+
* to re-send", and the call would run twice.
|
|
506
|
+
*/
|
|
507
|
+
export type ErrorContext = ErrorContextBase &
|
|
508
|
+
(
|
|
509
|
+
| {
|
|
510
|
+
readonly mode: "query"
|
|
511
|
+
/** Ignored: a query has no accepted state. */
|
|
512
|
+
readonly accepted?: boolean
|
|
513
|
+
}
|
|
514
|
+
| {
|
|
515
|
+
readonly mode: "update"
|
|
516
|
+
/**
|
|
517
|
+
* Whether the request may already be in the IC. Pass `false` only when
|
|
518
|
+
* you know it is not: no attempt of this call got past the replica's
|
|
519
|
+
* front door. Pass `true`:
|
|
520
|
+
*
|
|
521
|
+
* - once the agent's `onPollingStarted` has fired (the replica answered
|
|
522
|
+
* 202). From then on an HTTP error, such as a 429 from a rate-limited
|
|
523
|
+
* `read_state` or an expired delegation, says nothing about the
|
|
524
|
+
* update, so it classifies as `outcome_unknown`, not as `not_delivered`.
|
|
525
|
+
* - when the agent has re-sent this request itself after a failure that
|
|
526
|
+
* could have followed a delivery, a fetch exception or a 5xx. The
|
|
527
|
+
* agent keeps only the status of its last attempt, so a 429 that ends
|
|
528
|
+
* such a sequence proves nothing about the first one. The simplest way
|
|
529
|
+
* to keep the history out of the picture is to build the agent that
|
|
530
|
+
* sends updates with `retryTimes: 0` and let {@link retryUpdate} do
|
|
531
|
+
* the re-sending, which it only does when it is safe.
|
|
532
|
+
*
|
|
533
|
+
* Anything other than an explicit `false` is read as `true`, so an
|
|
534
|
+
* untyped caller that leaves it out gets the safe answer.
|
|
535
|
+
*/
|
|
536
|
+
readonly accepted: boolean
|
|
537
|
+
/**
|
|
538
|
+
* Whether the call re-sends a write that `resendOf()` offered, whose
|
|
539
|
+
* earlier attempt may have run. A signer that does not sign the
|
|
540
|
+
* re-send leaves that write's outcome unknown.
|
|
541
|
+
*/
|
|
542
|
+
readonly again?: boolean
|
|
543
|
+
}
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
// Which reject codes prove that an update call changed nothing.
|
|
547
|
+
//
|
|
548
|
+
// Per the IC interface specification (docs/references/ic-interface-spec.md in
|
|
549
|
+
// dfinity/portal, section "Reject codes" and the rules of "Abstract behavior"):
|
|
550
|
+
//
|
|
551
|
+
// - SYS_FATAL (1) and SYS_TRANSIENT (2) reach an ingress call only from
|
|
552
|
+
// "Request rejection" ("The IC may reject a received message for internal
|
|
553
|
+
// reasons ... Code = SYS_FATAL or Code = SYS_TRANSIENT"), before the request
|
|
554
|
+
// is processed, and SYS_TRANSIENT also from "Calls to frozen canisters are
|
|
555
|
+
// rejected", which happens before a call context is created. No canister
|
|
556
|
+
// code runs. Exception: a pending `stop_canister` on the management canister
|
|
557
|
+
// "may be rejected by the system at any time (the canister stays stopping in
|
|
558
|
+
// this case)" with SYS_TRANSIENT, after its effect, so calls to aaaaa-aa
|
|
559
|
+
// are not given this guarantee.
|
|
560
|
+
// - DESTINATION_INVALID (3): "Invalid destination (e.g. canister/account does
|
|
561
|
+
// not exist)": there was nothing to run.
|
|
562
|
+
// - CANISTER_REJECT (4) is "always" what `ic0.msg_reject` produces ("the
|
|
563
|
+
// canister cannot freely specify the reject code"); the execution that
|
|
564
|
+
// rejects returns normally, and "If message execution returns ..., the
|
|
565
|
+
// state is updated". So the canister's changes stand.
|
|
566
|
+
// - CANISTER_ERROR (5) covers a trap, whose "state mutation is discarded"
|
|
567
|
+
// only for the trapping execution: executions of the same call before an
|
|
568
|
+
// inter-canister await were already committed. It also covers call-context
|
|
569
|
+
// starvation, where "the state changes are persisted even when the IC is
|
|
570
|
+
// set to synthesize a CANISTER_ERROR reject". It can also mean the canister
|
|
571
|
+
// was stopped and never ran, but a client cannot tell which.
|
|
572
|
+
// - SYS_UNKNOWN (6) "is only applicable to inter-canister calls that used
|
|
573
|
+
// ic0.call_with_best_effort_response"; seen on an ingress call it proves
|
|
574
|
+
// nothing, so it counts as unknown.
|
|
575
|
+
//
|
|
576
|
+
// So: 1, 2, 3 are no effect (2 alone is also safe to re-send); 4, 5, 6 and any
|
|
577
|
+
// code not listed may have executed.
|
|
578
|
+
const SYS_FATAL = 1
|
|
579
|
+
const SYS_TRANSIENT = 2
|
|
580
|
+
const DESTINATION_INVALID = 3
|
|
581
|
+
|
|
582
|
+
const REJECT_NAMES: Readonly<Record<number, string>> = {
|
|
583
|
+
1: "SYS_FATAL",
|
|
584
|
+
2: "SYS_TRANSIENT",
|
|
585
|
+
3: "DESTINATION_INVALID",
|
|
586
|
+
4: "CANISTER_REJECT",
|
|
587
|
+
5: "CANISTER_ERROR",
|
|
588
|
+
6: "SYS_UNKNOWN",
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Agent error codes thrown while the agent builds, encodes or signs a request,
|
|
593
|
+
* before it calls `fetch` (read in `@icp-sdk/core` 6.1: `cbor.js`, `der.js`,
|
|
594
|
+
* `agent/http/index.js`, `polling/index.js`, `actor.js`). Nothing of that
|
|
595
|
+
* request left the process, and the same input fails the same way again, so
|
|
596
|
+
* none of them is worth a retry.
|
|
597
|
+
*/
|
|
598
|
+
const PRE_SEND_CODES: ReadonlySet<string> = new Set([
|
|
599
|
+
"CborEncodeErrorCode",
|
|
600
|
+
"DerEncodeErrorCode",
|
|
601
|
+
"IdentityInvalidErrorCode",
|
|
602
|
+
"MissingFetchErrorCode",
|
|
603
|
+
"CreateHttpAgentErrorCode",
|
|
604
|
+
"MissingCanisterIdErrorCode",
|
|
605
|
+
"InvalidReadStateRequestErrorCode",
|
|
606
|
+
])
|
|
607
|
+
|
|
608
|
+
/** The errors {@link notSigned} made, which {@link classifyError} unwraps. */
|
|
609
|
+
const unsigned = new WeakSet<object>()
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* What a failure of an identity's `transformRequest` is thrown to the agent
|
|
613
|
+
* as (see `agentFor`): a user who declines a passkey or hardware-wallet
|
|
614
|
+
* prompt, a signer that is gone. {@link classifyError} reads it as a request
|
|
615
|
+
* that was not signed, whatever the signer's error (its `cause`) says, and
|
|
616
|
+
* makes that error the result's `cause`.
|
|
617
|
+
*/
|
|
618
|
+
export const notSigned = (cause: unknown): Error => {
|
|
619
|
+
const error = Object.assign(new Error("the signer did not sign"), { cause })
|
|
620
|
+
unsigned.add(error)
|
|
621
|
+
return error
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The signer's error, when `error` is a {@link notSigned} failure or wraps
|
|
626
|
+
* one: the agent throws a failure from inside its own error handling (a
|
|
627
|
+
* query's `read_state` for the subnet's keys) as
|
|
628
|
+
* `UnknownError(UnexpectedErrorCode(error))`.
|
|
629
|
+
*/
|
|
630
|
+
function signerError(
|
|
631
|
+
error: unknown,
|
|
632
|
+
depth = 0
|
|
633
|
+
): { cause: unknown } | undefined {
|
|
634
|
+
if (depth > 3) return undefined
|
|
635
|
+
const record = asRecord(error)
|
|
636
|
+
if (unsigned.has(record)) return { cause: record.cause }
|
|
637
|
+
return signerError(asRecord(record.code).error, depth + 1)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* `ReactorError.code` when the IC answers that the canister a request was
|
|
642
|
+
* addressed to does not exist.
|
|
643
|
+
*/
|
|
644
|
+
const CANISTER_NOT_FOUND = "canister_not_found"
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* The error line of the body a boundary node answers with HTTP 400 for a
|
|
648
|
+
* canister id outside every subnet's range (the HTTP gateway of a local
|
|
649
|
+
* network answers the same): `error: canister_not_found`, then a `details:`
|
|
650
|
+
* line. The cause token is the gateway's machine-readable marker; the details
|
|
651
|
+
* line is free text and is not read. With the `m` flag, `$` also matches
|
|
652
|
+
* before a `\r`, so a CRLF body reads the same.
|
|
653
|
+
*/
|
|
654
|
+
const CANISTER_NOT_FOUND_BODY = /^error:[ \t]*canister_not_found[ \t]*$/m
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* The IC error code (`CanisterNotFound`) a replica puts on a reject 3 for a
|
|
658
|
+
* canister id inside its subnet's range that holds no canister. Mainnet
|
|
659
|
+
* answers a query and an update that way, and so does the fake replica of
|
|
660
|
+
* `@ic-reactor/core/testing`.
|
|
661
|
+
*/
|
|
662
|
+
const IC_CANISTER_NOT_FOUND = "IC0301"
|
|
663
|
+
|
|
664
|
+
/** The longest detail text a ReactorError's `message` carries. */
|
|
665
|
+
const MAX_DETAIL = 160
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* What the classifier reads off whatever was thrown. Every field comes from a
|
|
669
|
+
* property read, so a plain object of the same shape reads the same as the
|
|
670
|
+
* agent's own classes.
|
|
671
|
+
*/
|
|
672
|
+
interface Shape {
|
|
673
|
+
/** The agent's `ErrorKindEnum` value, such as `"Reject"` or `"Transport"`. */
|
|
674
|
+
readonly kind: string | undefined
|
|
675
|
+
/** The `name` of the agent's `ErrorCode`, such as `"HttpErrorCode"`. */
|
|
676
|
+
readonly codeName: string | undefined
|
|
677
|
+
readonly rejectCode: number | undefined
|
|
678
|
+
/** The IC error code of a reject, such as `"IC0301"`. */
|
|
679
|
+
readonly rejectErrorCode: string | undefined
|
|
680
|
+
readonly httpStatus: number | undefined
|
|
681
|
+
/** The body of an HTTP refusal, unshortened. */
|
|
682
|
+
readonly bodyText: string | undefined
|
|
683
|
+
/** Short human text from the error: a reject message, an HTTP body, ... */
|
|
684
|
+
readonly detail: string
|
|
685
|
+
readonly statusText: string
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** `value` when it is an object, so its fields can be read; `{}` otherwise. */
|
|
689
|
+
export const asRecord = (value: unknown): Record<string, unknown> =>
|
|
690
|
+
typeof value === "object" && value !== null
|
|
691
|
+
? (value as Record<string, unknown>)
|
|
692
|
+
: {}
|
|
693
|
+
|
|
694
|
+
const asString = (value: unknown): string | undefined =>
|
|
695
|
+
typeof value === "string" ? value : undefined
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* The reject code of a `RejectError`. The agent nests it (the error's `code` is
|
|
699
|
+
* a `...RejectErrorCode` carrying `rejectCode`); the flat position is read too,
|
|
700
|
+
* so a hand-built or future-shaped error still classifies.
|
|
701
|
+
*/
|
|
702
|
+
function readRejectCode(error: Record<string, unknown>): number | undefined {
|
|
703
|
+
const flat = error.rejectCode
|
|
704
|
+
if (typeof flat === "number") return flat
|
|
705
|
+
const nested = asRecord(error.code).rejectCode
|
|
706
|
+
return typeof nested === "number" ? nested : undefined
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* The HTTP status of a `ProtocolError` whose `code` is an `HttpErrorCode`.
|
|
711
|
+
* Recognised by the code's `name` and `status`, not with `instanceof`, so an
|
|
712
|
+
* error from another copy of `@icp-sdk/core` classifies the same way. A
|
|
713
|
+
* `status` on any other code says nothing about an HTTP answer and is ignored.
|
|
714
|
+
*/
|
|
715
|
+
export function readHttpStatus(error: unknown): number | undefined {
|
|
716
|
+
const code = asRecord(asRecord(error).code)
|
|
717
|
+
return code.name === "HttpErrorCode" && typeof code.status === "number"
|
|
718
|
+
? code.status
|
|
719
|
+
: undefined
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* One line of at most {@link MAX_DETAIL} characters. The agent's own messages
|
|
724
|
+
* run to many lines (headers, the request context, an HTTP body), which would
|
|
725
|
+
* bury what a person reading a toast or a log needs.
|
|
726
|
+
*/
|
|
727
|
+
function condense(text: string | undefined): string {
|
|
728
|
+
if (text === undefined) return ""
|
|
729
|
+
const line = text.replace(/\s+/g, " ").trim()
|
|
730
|
+
return line.length > MAX_DETAIL ? `${line.slice(0, MAX_DETAIL - 1)}…` : line
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function messageOf(value: unknown): string | undefined {
|
|
734
|
+
return typeof value === "string" ? value : asString(asRecord(value).message)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function readShape(error: unknown): Shape {
|
|
738
|
+
const record = asRecord(error)
|
|
739
|
+
const code = asRecord(record.code)
|
|
740
|
+
const codeName = asString(code.name)
|
|
741
|
+
const rejectCode = readRejectCode(record)
|
|
742
|
+
const httpStatus = readHttpStatus(record)
|
|
743
|
+
// The text that says the most, per code. An HTTP refusal's body names the
|
|
744
|
+
// problem; a reject carries the canister's or the IC's message; a transport
|
|
745
|
+
// failure wraps the fetch error. Anything else falls back to the error's
|
|
746
|
+
// own message, which a non-agent error keeps short.
|
|
747
|
+
const detail =
|
|
748
|
+
asString(code.rejectMessage) ??
|
|
749
|
+
asString(code.bodyText) ??
|
|
750
|
+
(codeName === "HttpFetchErrorCode" ? messageOf(code.error) : undefined) ??
|
|
751
|
+
asString(code.message) ??
|
|
752
|
+
messageOf(error)
|
|
753
|
+
return {
|
|
754
|
+
kind: asString(record.kind),
|
|
755
|
+
codeName,
|
|
756
|
+
rejectCode,
|
|
757
|
+
// Flat first, like the reject code, so a hand-built error reads the same.
|
|
758
|
+
rejectErrorCode:
|
|
759
|
+
asString(record.rejectErrorCode) ?? asString(code.rejectErrorCode),
|
|
760
|
+
httpStatus,
|
|
761
|
+
bodyText: asString(code.bodyText),
|
|
762
|
+
detail: condense(detail),
|
|
763
|
+
statusText: condense(asString(code.statusText)),
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Whether the answer says that the canister the request was addressed to does
|
|
769
|
+
* not exist: an HTTP 400 whose body's error line is `canister_not_found`, or
|
|
770
|
+
* a reject 3 carrying the IC error code `IC0301`. Read from the shape only, so
|
|
771
|
+
* any other 400 or reject 3 says nothing of the kind.
|
|
772
|
+
*/
|
|
773
|
+
const saysCanisterNotFound = (shape: Shape): boolean =>
|
|
774
|
+
shape.httpStatus === 400
|
|
775
|
+
? CANISTER_NOT_FOUND_BODY.test(shape.bodyText ?? "")
|
|
776
|
+
: shape.rejectCode === DESTINATION_INVALID &&
|
|
777
|
+
shape.rejectErrorCode === IC_CANISTER_NOT_FOUND
|
|
778
|
+
|
|
779
|
+
/** Whether `error`, or what it wraps, is an abort. */
|
|
780
|
+
function isAbort(error: unknown, depth = 0): boolean {
|
|
781
|
+
if (depth > 3) return false
|
|
782
|
+
const record = asRecord(error)
|
|
783
|
+
if (record.name === "AbortError") return true
|
|
784
|
+
// The agent wraps a fetch failure, abort included, as
|
|
785
|
+
// `TransportError(HttpFetchErrorCode(error))`.
|
|
786
|
+
return isAbort(asRecord(record.code).error, depth + 1)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const withDetail = (lead: string, detail: string): string =>
|
|
790
|
+
detail === "" ? lead : `${lead}: ${detail}`
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Classifies a failure from the agent (or anything thrown around it) into a
|
|
794
|
+
* {@link ReactorError}, following one table. `mayHaveExecuted` is `true`
|
|
795
|
+
* exactly when the failure leaves open that the canister ran the call.
|
|
796
|
+
*
|
|
797
|
+
* | Failure | kind (update / query) | mayHaveExecuted (update / query) |
|
|
798
|
+
* | --- | --- | --- |
|
|
799
|
+
* | already a ReactorError | unchanged | unchanged |
|
|
800
|
+
* | `AbortError`, or the signal aborted | `cancelled` | true / false |
|
|
801
|
+
* | reject 1 or 3 | `rejected` | false / false |
|
|
802
|
+
* | reject 2 | `not_delivered`, retryable | false / false |
|
|
803
|
+
* | reject 1, 2 or 3 from `aaaaa-aa` | `rejected` | true / false |
|
|
804
|
+
* | reject 4 or 5 | `rejected` | true / false |
|
|
805
|
+
* | reject 6, an unknown code, HTTP 408 or 5xx, a network failure, a polling timeout, a `Trust` failure | `outcome_unknown` / `not_delivered`, retryable for a query | true / false |
|
|
806
|
+
* | HTTP 429 | `not_delivered`, retryable | false / false |
|
|
807
|
+
* | any other HTTP 4xx, `IngressExpiryInvalid` | `not_delivered` | false / false |
|
|
808
|
+
* | a request that could not be built or signed (CBOR or DER encoding, an invalid identity, no `fetch`, no canister id) | `not_delivered` | false / false |
|
|
809
|
+
* | the signer did not sign a request (a declined prompt: {@link notSigned}) | `not_delivered` | false / false |
|
|
810
|
+
*
|
|
811
|
+
* A canister that does not exist also sets `code: "canister_not_found"`, and
|
|
812
|
+
* changes nothing else. The IC says it in one of two ways. For a canister id
|
|
813
|
+
* outside every subnet's range, a boundary node answers HTTP 400 with a body
|
|
814
|
+
* whose error line is `canister_not_found` (a row of "any other HTTP 4xx").
|
|
815
|
+
* For an id inside a subnet's range that holds no canister, the replica
|
|
816
|
+
* rejects with code 3 and the IC error code `IC0301`: mainnet does, for a
|
|
817
|
+
* query and an update, and so does the fake replica of
|
|
818
|
+
* `@ic-reactor/core/testing` (a row of "reject 1 or 3", or of the `aaaaa-aa`
|
|
819
|
+
* row for a call to the management canister). No other 400 and no other
|
|
820
|
+
* reject gets the code. The code names the canister the request was routed
|
|
821
|
+
* to: for `aaaaa-aa`, the one its arguments name. It stays on an update that
|
|
822
|
+
* may already be in the IC, whose 400 reads as `outcome_unknown` like any
|
|
823
|
+
* other, and is never set on `cancelled`.
|
|
824
|
+
*
|
|
825
|
+
* For a query nothing that matters executes, so every doubt is `not_delivered`
|
|
826
|
+
* and `mayHaveExecuted` is `false`. The HTTP rows, and `IngressExpiryInvalid`,
|
|
827
|
+
* hold for an update only when `context.accepted` is `false`, and so do the
|
|
828
|
+
* rows of a request that could not be built or signed. Once the request
|
|
829
|
+
* may be in the IC (see {@link ErrorContext}), they all read as
|
|
830
|
+
* `outcome_unknown` with `mayHaveExecuted: true`, because a refusal that comes
|
|
831
|
+
* later says nothing about the call.
|
|
832
|
+
*
|
|
833
|
+
* An abort during an update is `cancelled` with `mayHaveExecuted: true`: the
|
|
834
|
+
* request may already have been delivered. Use
|
|
835
|
+
* `createReactorError("cancelled", ...)` for a refusal before anything was
|
|
836
|
+
* sent, which says `false`.
|
|
837
|
+
*
|
|
838
|
+
* An error that is already a ReactorError comes back as the same object, so
|
|
839
|
+
* it may carry an `err` that the return type does not promise.
|
|
840
|
+
*
|
|
841
|
+
* The thrown error becomes the result's `cause` (for a signer that did not
|
|
842
|
+
* sign, the signer's own error), and the result's `message`
|
|
843
|
+
* is one short line (`[ic-reactor] <method> on <canisterId>: <reason>`): a
|
|
844
|
+
* missing canister arrives from the agent as an HTTP 400 whose own message
|
|
845
|
+
* runs to thousands of characters.
|
|
846
|
+
*/
|
|
847
|
+
export function classifyError(
|
|
848
|
+
error: unknown,
|
|
849
|
+
context: ErrorContext
|
|
850
|
+
): ReactorError {
|
|
851
|
+
if (isReactorError(error)) return error as unknown as ReactorError
|
|
852
|
+
const { method, canisterId } = context
|
|
853
|
+
const update = context.mode === "update"
|
|
854
|
+
// Fail closed: only an explicit `false` says the request never got in.
|
|
855
|
+
const mayBeAccepted = update && context.accepted !== false
|
|
856
|
+
const shape = readShape(error)
|
|
857
|
+
const { rejectCode, httpStatus } = shape
|
|
858
|
+
const signer = signerError(error)
|
|
859
|
+
const thrown = signer === undefined ? error : signer.cause
|
|
860
|
+
|
|
861
|
+
const make = (
|
|
862
|
+
kind: ReactorErrorKind,
|
|
863
|
+
mayHaveExecuted: boolean,
|
|
864
|
+
reason: string,
|
|
865
|
+
retryable = false,
|
|
866
|
+
code?: string
|
|
867
|
+
): ReactorError =>
|
|
868
|
+
build({
|
|
869
|
+
kind,
|
|
870
|
+
mayHaveExecuted,
|
|
871
|
+
reason,
|
|
872
|
+
retryable,
|
|
873
|
+
code,
|
|
874
|
+
method,
|
|
875
|
+
canisterId,
|
|
876
|
+
rejectCode,
|
|
877
|
+
httpStatus,
|
|
878
|
+
cause: thrown,
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
if (context.signal?.aborted || isAbort(thrown)) {
|
|
882
|
+
return make("cancelled", update, DEFAULT_REASON.cancelled)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Before anything else the agent wrapped it in: what the signer threw says
|
|
886
|
+
// nothing about the call. Only a poll of an update the IC may already hold
|
|
887
|
+
// leaves the outcome open, and a re-send of an offered write, whose earlier
|
|
888
|
+
// attempt may have run (as a re-send that times out does). A read's
|
|
889
|
+
// signature is asked for again by any retry, so a read is not retried.
|
|
890
|
+
if (signer !== undefined) {
|
|
891
|
+
const unknown =
|
|
892
|
+
mayBeAccepted || (context.mode === "update" && context.again === true)
|
|
893
|
+
const reason = mayBeAccepted
|
|
894
|
+
? "the call was sent, but the signer did not sign a request for its answer"
|
|
895
|
+
: `the signer did not sign the request${update ? ", so it was not sent" : ""}`
|
|
896
|
+
return make(
|
|
897
|
+
unknown ? "outcome_unknown" : "not_delivered",
|
|
898
|
+
unknown,
|
|
899
|
+
withDetail(reason, condense(messageOf(thrown)))
|
|
900
|
+
)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// An agent error has a `kind`. One without (a bug, or some other agent) is
|
|
904
|
+
// classified the same way but never retried: nothing says another attempt
|
|
905
|
+
// would end differently.
|
|
906
|
+
const agentShaped =
|
|
907
|
+
shape.kind !== undefined ||
|
|
908
|
+
rejectCode !== undefined ||
|
|
909
|
+
httpStatus !== undefined
|
|
910
|
+
|
|
911
|
+
// A query executes nothing that matters, so every doubt is "not delivered".
|
|
912
|
+
const doubt = (reason: string): ReactorError =>
|
|
913
|
+
update
|
|
914
|
+
? make("outcome_unknown", true, reason)
|
|
915
|
+
: make("not_delivered", false, reason, agentShaped)
|
|
916
|
+
|
|
917
|
+
if (shape.kind === "Reject" || rejectCode !== undefined) {
|
|
918
|
+
const name = rejectCode === undefined ? undefined : REJECT_NAMES[rejectCode]
|
|
919
|
+
const reason = withDetail(
|
|
920
|
+
`the IC rejected the call${
|
|
921
|
+
rejectCode === undefined
|
|
922
|
+
? ""
|
|
923
|
+
: ` with code ${rejectCode}${name === undefined ? "" : ` (${name})`}`
|
|
924
|
+
}`,
|
|
925
|
+
shape.detail
|
|
926
|
+
)
|
|
927
|
+
// aaaaa-aa is exempt from the "no canister code ran" reading of 1, 2 and 3.
|
|
928
|
+
const noCodeRan = canisterId !== MANAGEMENT_CANISTER
|
|
929
|
+
if (rejectCode === SYS_TRANSIENT && noCodeRan) {
|
|
930
|
+
return make("not_delivered", false, reason, true)
|
|
931
|
+
}
|
|
932
|
+
if (
|
|
933
|
+
rejectCode === SYS_TRANSIENT ||
|
|
934
|
+
rejectCode === SYS_FATAL ||
|
|
935
|
+
rejectCode === DESTINATION_INVALID
|
|
936
|
+
) {
|
|
937
|
+
const code = saysCanisterNotFound(shape) ? CANISTER_NOT_FOUND : undefined
|
|
938
|
+
return make("rejected", !noCodeRan && update, reason, false, code)
|
|
939
|
+
}
|
|
940
|
+
if (rejectCode === 4 || rejectCode === 5) {
|
|
941
|
+
return make("rejected", update, reason)
|
|
942
|
+
}
|
|
943
|
+
// 6, a code outside 1 to 6, or a rejection whose code cannot be read.
|
|
944
|
+
return doubt(reason)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (shape.codeName !== undefined && PRE_SEND_CODES.has(shape.codeName)) {
|
|
948
|
+
const reason = withDetail(
|
|
949
|
+
"the request could not be built or signed, so it was not sent",
|
|
950
|
+
shape.detail
|
|
951
|
+
)
|
|
952
|
+
// A request that could not be built says nothing about one sent before
|
|
953
|
+
// it: for an update the replica may already hold (a poll that could not
|
|
954
|
+
// be encoded), the call's outcome is still unknown.
|
|
955
|
+
return mayBeAccepted
|
|
956
|
+
? make("outcome_unknown", true, reason)
|
|
957
|
+
: make("not_delivered", false, reason)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (shape.codeName === "IngressExpiryInvalidErrorCode") {
|
|
961
|
+
const reason = withDetail(
|
|
962
|
+
"the replica refused the request's ingress expiry",
|
|
963
|
+
shape.detail
|
|
964
|
+
)
|
|
965
|
+
return mayBeAccepted
|
|
966
|
+
? make("outcome_unknown", true, reason)
|
|
967
|
+
: make("not_delivered", false, reason)
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (httpStatus !== undefined) {
|
|
971
|
+
const reason = withDetail(
|
|
972
|
+
`the replica answered HTTP ${httpStatus}${
|
|
973
|
+
shape.statusText === "" ? "" : ` ${shape.statusText}`
|
|
974
|
+
}`,
|
|
975
|
+
shape.detail
|
|
976
|
+
)
|
|
977
|
+
// A 429 or a 4xx is the replica or a boundary node refusing the request
|
|
978
|
+
// itself (rate limit, bad signature, expired delegation, no such
|
|
979
|
+
// canister): the canister never saw it. 408 is the exception, a timeout
|
|
980
|
+
// after the request may have been passed on. 5xx may follow a delivery.
|
|
981
|
+
const refused = httpStatus >= 400 && httpStatus < 500 && httpStatus !== 408
|
|
982
|
+
if (!refused) return doubt(reason)
|
|
983
|
+
// The code says what the answer said; `kind` still says what it proves.
|
|
984
|
+
const code = saysCanisterNotFound(shape) ? CANISTER_NOT_FOUND : undefined
|
|
985
|
+
if (mayBeAccepted) return make("outcome_unknown", true, reason, false, code)
|
|
986
|
+
return make("not_delivered", false, reason, httpStatus === 429, code)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Transport, Protocol, Trust, External, Limit, Unknown, a polling timeout,
|
|
990
|
+
// and anything that is not an agent error at all.
|
|
991
|
+
const lead =
|
|
992
|
+
shape.kind === "Transport"
|
|
993
|
+
? "could not reach the replica"
|
|
994
|
+
: shape.kind === "Trust"
|
|
995
|
+
? "the replica's answer could not be verified"
|
|
996
|
+
: "the call failed"
|
|
997
|
+
return doubt(withDetail(lead, shape.detail || "unknown error"))
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// ---------------------------------------------------------------------------
|
|
1001
|
+
// Retry
|
|
1002
|
+
// ---------------------------------------------------------------------------
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Whether the classifier marked `error` as safe to send again: a
|
|
1006
|
+
* `not_delivered` that another attempt can change. {@link retryQuery} and
|
|
1007
|
+
* {@link retryUpdate} add their own limits; a direct read re-sends on this
|
|
1008
|
+
* alone, at most twice.
|
|
1009
|
+
*
|
|
1010
|
+
* It narrows `error` to a {@link ReactorError} when it returns `true`. Its
|
|
1011
|
+
* `false` says nothing about the type, though TypeScript reads it as "not a
|
|
1012
|
+
* `ReactorError`", so call it on an `unknown`, as both callers do.
|
|
1013
|
+
*/
|
|
1014
|
+
export const isRetryable = (error: unknown): error is ReactorError =>
|
|
1015
|
+
isReactorError(error) &&
|
|
1016
|
+
error.kind === "not_delivered" &&
|
|
1017
|
+
(error as unknown as Record<symbol, unknown>)[RETRYABLE] === true
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* How long to wait before each re-send of an update: 300 ms, then 600 ms.
|
|
1021
|
+
* Its length is the most re-sends one call gets.
|
|
1022
|
+
*/
|
|
1023
|
+
export const UPDATE_RESEND_DELAYS_MS: readonly [300, 600] = [300, 600]
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Whether an update may be sent again. Every attempt at an update is a new
|
|
1027
|
+
* request id, so the IC cannot tell a retry from a second call and runs both.
|
|
1028
|
+
* A re-send is safe only when the failure proves the first attempt was never
|
|
1029
|
+
* accepted, and only two do: a SysTransient reject (code 2, from anything but
|
|
1030
|
+
* the management canister) and an HTTP 429. Any other failure, a network
|
|
1031
|
+
* error included, may have come after the replica accepted the call.
|
|
1032
|
+
*
|
|
1033
|
+
* The 429 proof holds for one attempt. The agent can re-send a request on its
|
|
1034
|
+
* own (`retryTimes`, three by default), and it reports only the status of the
|
|
1035
|
+
* last attempt, so a 429 that follows a fetch failure or a 5xx may hide an
|
|
1036
|
+
* earlier delivery. The client must therefore either build the agent that sends
|
|
1037
|
+
* updates with `retryTimes: 0`, or classify with `accepted: true` when an
|
|
1038
|
+
* earlier attempt may have been delivered; see {@link ErrorContext}.
|
|
1039
|
+
*
|
|
1040
|
+
* `resendsSoFar` is how many times this call has been re-sent already; the
|
|
1041
|
+
* answer is `false` once it reaches the length of
|
|
1042
|
+
* {@link UPDATE_RESEND_DELAYS_MS}.
|
|
1043
|
+
*/
|
|
1044
|
+
export function retryUpdate(error: unknown, resendsSoFar: number): boolean {
|
|
1045
|
+
if (resendsSoFar >= UPDATE_RESEND_DELAYS_MS.length) return false
|
|
1046
|
+
// A retryable error is a `not_delivered`, whose `mayHaveExecuted` is always
|
|
1047
|
+
// `false`, so that needs no check of its own here.
|
|
1048
|
+
return (
|
|
1049
|
+
isRetryable(error) &&
|
|
1050
|
+
(error.rejectCode === SYS_TRANSIENT || error.httpStatus === 429)
|
|
1051
|
+
)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* A TanStack Query `retry` for a query: at most 3 retries, and only of a
|
|
1056
|
+
* failure the classifier marked as safe to try again (a `not_delivered` that a
|
|
1057
|
+
* later attempt can change). Never on a server: TanStack defaults to no
|
|
1058
|
+
* retries there, and supplying a predicate would override that and add the
|
|
1059
|
+
* 1s/2s/4s backoff to a request that used to fail fast.
|
|
1060
|
+
*
|
|
1061
|
+
* `failureCount` is how many failures there have been before this one, as
|
|
1062
|
+
* TanStack passes it (0 after the first failure).
|
|
1063
|
+
*/
|
|
1064
|
+
export function retryQuery(failureCount: number, error: unknown): boolean {
|
|
1065
|
+
if (isServer()) return false
|
|
1066
|
+
return failureCount < 3 && isRetryable(error)
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* The default `retry` of a client's `QueryClient`. A client's own reads set
|
|
1071
|
+
* `retry` themselves, so this default reaches only the other queries on that
|
|
1072
|
+
* `QueryClient`, such as an app's REST queries. A `ReactorError` is retried as
|
|
1073
|
+
* {@link retryQuery} says; any other error as TanStack Query retries it by
|
|
1074
|
+
* default: up to 3 times in a browser, never on a server (#903).
|
|
1075
|
+
*/
|
|
1076
|
+
export function retryDefault(failureCount: number, error: unknown): boolean {
|
|
1077
|
+
if (isReactorError(error)) return retryQuery(failureCount, error)
|
|
1078
|
+
return !isServer() && failureCount < 3
|
|
1079
|
+
}
|