@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,1492 @@
|
|
|
1
|
+
// The form engine: a draft tree over @meantjs/schema's public API, with
|
|
2
|
+
// no React in it. Ported from the candid-forms lab (engine.ts), with its
|
|
3
|
+
// fixes: own-property reads and writes for keys like "__proto__",
|
|
4
|
+
// inhabitation-aware defaults, a tolerant draftFromValue, and issues on the
|
|
5
|
+
// codec's `$args[i]…` paths.
|
|
6
|
+
//
|
|
7
|
+
// Value model: exactly what validate()/encodeArgs() and ic-reactor accept.
|
|
8
|
+
// bigint (nat, int, nat64, int64) | number (nat8…int32, float32/64)
|
|
9
|
+
// | string (text) | boolean | null | Principal (canonical text)
|
|
10
|
+
// | Uint8Array (blob) | T | null (opt) | { some: T } | null (boxed opt)
|
|
11
|
+
// | T[] (vec) | {} (unit) | object (record) | array (tuple)
|
|
12
|
+
// | { tag } / { tag, value } (variant) | { principal, method } (func)
|
|
13
|
+
// | Principal (service)
|
|
14
|
+
import {
|
|
15
|
+
isBoxedOpt,
|
|
16
|
+
isPrincipal,
|
|
17
|
+
type AnyFieldSchema,
|
|
18
|
+
type AnySchema,
|
|
19
|
+
type ResolvedNode,
|
|
20
|
+
} from "@meantjs/schema"
|
|
21
|
+
import { encodeArgs } from "@meantjs/schema/codec"
|
|
22
|
+
import { validate } from "@meantjs/schema/validate"
|
|
23
|
+
import { toHex } from "../lib/hex.ts"
|
|
24
|
+
import { toVars } from "../lib/reactor.ts"
|
|
25
|
+
import {
|
|
26
|
+
armOf,
|
|
27
|
+
armsOf,
|
|
28
|
+
childPath,
|
|
29
|
+
displayLabel,
|
|
30
|
+
elementsOf,
|
|
31
|
+
fieldsOf,
|
|
32
|
+
getOwn,
|
|
33
|
+
isBytes,
|
|
34
|
+
isTagOnly,
|
|
35
|
+
numericLabelId,
|
|
36
|
+
orderFields,
|
|
37
|
+
resolve,
|
|
38
|
+
setOwn,
|
|
39
|
+
withOwn,
|
|
40
|
+
type RecordNode,
|
|
41
|
+
type TupleNode,
|
|
42
|
+
type VariantNode,
|
|
43
|
+
} from "../lib/schema.ts"
|
|
44
|
+
|
|
45
|
+
// Resolution, typed accessors, own-property access and labels live in
|
|
46
|
+
// lib/schema.ts (the value viewers use them too); the engine re-exports them
|
|
47
|
+
// as part of its surface.
|
|
48
|
+
export {
|
|
49
|
+
armOf,
|
|
50
|
+
armsOf,
|
|
51
|
+
childPath,
|
|
52
|
+
displayLabel,
|
|
53
|
+
elementsOf,
|
|
54
|
+
fieldsOf,
|
|
55
|
+
getOwn,
|
|
56
|
+
isBytes,
|
|
57
|
+
numericLabelId,
|
|
58
|
+
orderFields,
|
|
59
|
+
resolve,
|
|
60
|
+
setOwn,
|
|
61
|
+
withOwn,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Paths: validate()'s grammar. `$`, `.ident`, `["quoted key"]`, `[index]`.
|
|
66
|
+
// An unboxed opt's inner shares the opt's path; a boxed opt's inner is
|
|
67
|
+
// `.some`; a variant's payload is `.value` (tag issues at `.tag`); a func
|
|
68
|
+
// reference's parts are `.principal` and `.method`.
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/** Namespaces a per-argument `$…` path the way encodeArgs reports: `$args[i]…`. */
|
|
72
|
+
export function argPath(index: number, path = "$"): string {
|
|
73
|
+
return `$args[${index}]${path.slice(1)}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* An argument for people: its name in the .did when it has one
|
|
78
|
+
* (`(sequence_number : nat)`), "argument" when it is the only one, else
|
|
79
|
+
* "argument 2" (counted from 1).
|
|
80
|
+
*/
|
|
81
|
+
export function argLabel(
|
|
82
|
+
index: number,
|
|
83
|
+
argCount: number,
|
|
84
|
+
names?: readonly (string | undefined)[]
|
|
85
|
+
): string {
|
|
86
|
+
const name = names?.[index]
|
|
87
|
+
if (name !== undefined && name !== "") return name
|
|
88
|
+
return argCount === 1 ? "argument" : `argument ${index + 1}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A path for people, named like the form's labels: `$args[0].to.owner` →
|
|
93
|
+
* "to.owner" for a one-argument method, "argument 2.to.owner" for
|
|
94
|
+
* `$args[1]…` of several, "argument[3]" for an item of a lone vec.
|
|
95
|
+
*/
|
|
96
|
+
export function pathLabel(
|
|
97
|
+
path: string,
|
|
98
|
+
argCount: number,
|
|
99
|
+
names?: readonly (string | undefined)[]
|
|
100
|
+
): string {
|
|
101
|
+
const match = /^\$args\[(\d+)\](.*)$/.exec(path)
|
|
102
|
+
if (match === null) return path === "$" ? "arguments" : path
|
|
103
|
+
const [, index = "0", rest = ""] = match
|
|
104
|
+
const arg = argLabel(Number(index), argCount, names)
|
|
105
|
+
if (argCount === 1 && rest.startsWith(".")) return rest.slice(1)
|
|
106
|
+
return `${arg}${rest}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Controls: which editor a resolved node calls for.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const FIXED: Readonly<Record<string, readonly [number, number]>> = {
|
|
114
|
+
nat8: [0, 255],
|
|
115
|
+
nat16: [0, 65_535],
|
|
116
|
+
nat32: [0, 4_294_967_295],
|
|
117
|
+
int8: [-128, 127],
|
|
118
|
+
int16: [-32_768, 32_767],
|
|
119
|
+
int32: [-2_147_483_648, 2_147_483_647],
|
|
120
|
+
}
|
|
121
|
+
const BIG: Readonly<Record<string, { min?: bigint; max?: bigint }>> = {
|
|
122
|
+
nat: { min: 0n },
|
|
123
|
+
int: {},
|
|
124
|
+
nat64: { min: 0n, max: 2n ** 64n - 1n },
|
|
125
|
+
int64: { min: -(2n ** 63n), max: 2n ** 63n - 1n },
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ArmControl {
|
|
129
|
+
readonly tag: string
|
|
130
|
+
readonly tagOnly: boolean
|
|
131
|
+
/** No finite value exists for the payload: the arm cannot be chosen. */
|
|
132
|
+
readonly uninhabited: boolean
|
|
133
|
+
readonly schema: AnyFieldSchema
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type Control =
|
|
137
|
+
| { readonly control: "text" }
|
|
138
|
+
| { readonly control: "bool" }
|
|
139
|
+
| {
|
|
140
|
+
readonly control: "bigint"
|
|
141
|
+
readonly primitive: string
|
|
142
|
+
readonly min?: bigint
|
|
143
|
+
readonly max?: bigint
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
readonly control: "integer"
|
|
147
|
+
readonly primitive: string
|
|
148
|
+
readonly min: number
|
|
149
|
+
readonly max: number
|
|
150
|
+
}
|
|
151
|
+
| { readonly control: "float"; readonly bits: 32 | 64 }
|
|
152
|
+
| { readonly control: "principal" }
|
|
153
|
+
| { readonly control: "fixed"; readonly what: "null" | "reserved" | "unit" }
|
|
154
|
+
| { readonly control: "uninhabited" }
|
|
155
|
+
| { readonly control: "bytes" }
|
|
156
|
+
| {
|
|
157
|
+
readonly control: "optional"
|
|
158
|
+
readonly boxed: boolean
|
|
159
|
+
readonly inner: AnyFieldSchema
|
|
160
|
+
}
|
|
161
|
+
| { readonly control: "list"; readonly item: AnyFieldSchema }
|
|
162
|
+
| {
|
|
163
|
+
readonly control: "group"
|
|
164
|
+
readonly fields: readonly (readonly [string, AnyFieldSchema])[]
|
|
165
|
+
}
|
|
166
|
+
| { readonly control: "tuple"; readonly elements: readonly AnyFieldSchema[] }
|
|
167
|
+
| { readonly control: "choice"; readonly arms: readonly ArmControl[] }
|
|
168
|
+
| { readonly control: "funcReference"; readonly mode: string }
|
|
169
|
+
| { readonly control: "serviceReference" }
|
|
170
|
+
|
|
171
|
+
const controls = new WeakMap<ResolvedNode, Control>()
|
|
172
|
+
|
|
173
|
+
export function controlOf(schema: AnyFieldSchema): Control {
|
|
174
|
+
const node = resolve(schema)
|
|
175
|
+
let control = controls.get(node)
|
|
176
|
+
if (control === undefined) {
|
|
177
|
+
control = computeControl(node)
|
|
178
|
+
controls.set(node, control)
|
|
179
|
+
}
|
|
180
|
+
return control
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function computeControl(node: ResolvedNode): Control {
|
|
184
|
+
switch (node.kind) {
|
|
185
|
+
case "primitive": {
|
|
186
|
+
const p = node.primitive
|
|
187
|
+
const big = BIG[p]
|
|
188
|
+
const fixed = FIXED[p]
|
|
189
|
+
if (p === "text") return { control: "text" }
|
|
190
|
+
if (p === "bool") return { control: "bool" }
|
|
191
|
+
if (big !== undefined) return { control: "bigint", primitive: p, ...big }
|
|
192
|
+
if (fixed !== undefined)
|
|
193
|
+
return {
|
|
194
|
+
control: "integer",
|
|
195
|
+
primitive: p,
|
|
196
|
+
min: fixed[0],
|
|
197
|
+
max: fixed[1],
|
|
198
|
+
}
|
|
199
|
+
if (p === "float32") return { control: "float", bits: 32 }
|
|
200
|
+
if (p === "float64") return { control: "float", bits: 64 }
|
|
201
|
+
if (p === "principal") return { control: "principal" }
|
|
202
|
+
if (p === "null" || p === "reserved") return { control: "fixed", what: p }
|
|
203
|
+
if (p === "empty") return { control: "uninhabited" }
|
|
204
|
+
throw new TypeError(`unknown primitive ${p}`)
|
|
205
|
+
}
|
|
206
|
+
case "opt":
|
|
207
|
+
return { control: "optional", boxed: isBoxedOpt(node), inner: node.inner }
|
|
208
|
+
case "vec":
|
|
209
|
+
return { control: "list", item: node.inner }
|
|
210
|
+
case "blob":
|
|
211
|
+
return { control: "bytes" }
|
|
212
|
+
case "unit":
|
|
213
|
+
return { control: "fixed", what: "unit" }
|
|
214
|
+
case "record":
|
|
215
|
+
return { control: "group", fields: fieldsOf(node) }
|
|
216
|
+
case "tuple":
|
|
217
|
+
return { control: "tuple", elements: elementsOf(node) }
|
|
218
|
+
case "variant":
|
|
219
|
+
return {
|
|
220
|
+
control: "choice",
|
|
221
|
+
arms: armsOf(node).map(([tag, schema]) => ({
|
|
222
|
+
tag,
|
|
223
|
+
tagOnly: isTagOnly(schema),
|
|
224
|
+
uninhabited: isUninhabited(schema),
|
|
225
|
+
schema,
|
|
226
|
+
})),
|
|
227
|
+
}
|
|
228
|
+
case "func":
|
|
229
|
+
return { control: "funcReference", mode: node.mode }
|
|
230
|
+
case "service":
|
|
231
|
+
return { control: "serviceReference" }
|
|
232
|
+
default: {
|
|
233
|
+
const never: never = node
|
|
234
|
+
throw new TypeError(`unknown kind ${(never as { kind: string }).kind}`)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Whether a field is optional (`opt`), so it may be left out (null). */
|
|
240
|
+
export function isOptional(schema: AnyFieldSchema): boolean {
|
|
241
|
+
return resolve(schema).kind === "opt"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** A short Candid type for labels: "nat", "opt blob", "vec record", "variant". */
|
|
245
|
+
export function typeName(schema: AnyFieldSchema, depth = 0): string {
|
|
246
|
+
const node = resolve(schema)
|
|
247
|
+
switch (node.kind) {
|
|
248
|
+
case "primitive":
|
|
249
|
+
return node.primitive
|
|
250
|
+
case "opt":
|
|
251
|
+
return depth > 1 ? "opt …" : `opt ${typeName(node.inner, depth + 1)}`
|
|
252
|
+
case "vec":
|
|
253
|
+
return depth > 1 ? "vec …" : `vec ${typeName(node.inner, depth + 1)}`
|
|
254
|
+
case "blob":
|
|
255
|
+
return "blob"
|
|
256
|
+
case "unit":
|
|
257
|
+
return "record {}"
|
|
258
|
+
case "record":
|
|
259
|
+
return "record"
|
|
260
|
+
case "tuple":
|
|
261
|
+
return `tuple (${elementsOf(node).length})`
|
|
262
|
+
case "variant":
|
|
263
|
+
return "variant"
|
|
264
|
+
case "func":
|
|
265
|
+
return node.mode === "update" ? "func" : `func ${node.mode}`
|
|
266
|
+
case "service":
|
|
267
|
+
return "service"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Default values: the first finite value of each type, inhabitation-aware.
|
|
273
|
+
// opt → null and vec → [] break every legal recursion; a variant takes its
|
|
274
|
+
// first arm (in key order) with a finite value; a cycle on the active path,
|
|
275
|
+
// or `empty`, has none. Principals have no sensible default: "" (fails, so
|
|
276
|
+
// the field says "required") unless options.principal is given.
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
export const UNINHABITED: unique symbol = Symbol("uninhabited")
|
|
280
|
+
|
|
281
|
+
export interface DefaultOptions {
|
|
282
|
+
/** Fills principal fields (e.g. the caller); otherwise they start empty. */
|
|
283
|
+
readonly principal?: string
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function defaultValue(
|
|
287
|
+
schema: AnyFieldSchema,
|
|
288
|
+
options: DefaultOptions = {}
|
|
289
|
+
): unknown {
|
|
290
|
+
return defaultAt(schema, options, new Set())
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const inhabitation = new WeakMap<ResolvedNode, boolean>()
|
|
294
|
+
|
|
295
|
+
/** Whether no finite value of the type exists (`empty`, `record { x : Loop }`). */
|
|
296
|
+
export function isUninhabited(schema: AnyFieldSchema): boolean {
|
|
297
|
+
const node = resolve(schema)
|
|
298
|
+
let empty = inhabitation.get(node)
|
|
299
|
+
if (empty === undefined) {
|
|
300
|
+
// A fresh active set: the answer is then independent of where it is asked.
|
|
301
|
+
empty = defaultAt(schema, {}, new Set()) === UNINHABITED
|
|
302
|
+
inhabitation.set(node, empty)
|
|
303
|
+
}
|
|
304
|
+
return empty
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function defaultAt(
|
|
308
|
+
schema: AnyFieldSchema,
|
|
309
|
+
options: DefaultOptions,
|
|
310
|
+
active: Set<object>
|
|
311
|
+
): unknown {
|
|
312
|
+
const node = resolve(schema)
|
|
313
|
+
if (active.has(node)) return UNINHABITED
|
|
314
|
+
active.add(node)
|
|
315
|
+
try {
|
|
316
|
+
switch (node.kind) {
|
|
317
|
+
case "primitive":
|
|
318
|
+
switch (node.primitive) {
|
|
319
|
+
case "null":
|
|
320
|
+
case "reserved":
|
|
321
|
+
return null
|
|
322
|
+
case "bool":
|
|
323
|
+
return false
|
|
324
|
+
case "text":
|
|
325
|
+
return ""
|
|
326
|
+
case "principal":
|
|
327
|
+
return options.principal ?? ""
|
|
328
|
+
case "empty":
|
|
329
|
+
return UNINHABITED
|
|
330
|
+
case "nat":
|
|
331
|
+
case "int":
|
|
332
|
+
case "nat64":
|
|
333
|
+
case "int64":
|
|
334
|
+
return 0n
|
|
335
|
+
default:
|
|
336
|
+
return 0 // nat8…int32, float32, float64
|
|
337
|
+
}
|
|
338
|
+
case "opt":
|
|
339
|
+
return null
|
|
340
|
+
case "vec":
|
|
341
|
+
return []
|
|
342
|
+
case "blob":
|
|
343
|
+
return new Uint8Array(0)
|
|
344
|
+
case "unit":
|
|
345
|
+
return {}
|
|
346
|
+
case "record": {
|
|
347
|
+
const out: Record<string, unknown> = {}
|
|
348
|
+
for (const [key, field] of fieldsOf(node)) {
|
|
349
|
+
const value = defaultAt(field, options, active)
|
|
350
|
+
if (value === UNINHABITED) return UNINHABITED
|
|
351
|
+
setOwn(out, key, value)
|
|
352
|
+
}
|
|
353
|
+
return out
|
|
354
|
+
}
|
|
355
|
+
case "tuple": {
|
|
356
|
+
const out: unknown[] = []
|
|
357
|
+
for (const element of elementsOf(node)) {
|
|
358
|
+
const value = defaultAt(element, options, active)
|
|
359
|
+
if (value === UNINHABITED) return UNINHABITED
|
|
360
|
+
out.push(value)
|
|
361
|
+
}
|
|
362
|
+
return out
|
|
363
|
+
}
|
|
364
|
+
case "variant":
|
|
365
|
+
for (const [tag, arm] of armsOf(node)) {
|
|
366
|
+
if (isTagOnly(arm)) return { tag }
|
|
367
|
+
const value = defaultAt(arm, options, active)
|
|
368
|
+
if (value !== UNINHABITED) return { tag, value }
|
|
369
|
+
}
|
|
370
|
+
return UNINHABITED
|
|
371
|
+
case "func":
|
|
372
|
+
return { principal: options.principal ?? "", method: "" }
|
|
373
|
+
case "service":
|
|
374
|
+
return options.principal ?? ""
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
active.delete(node)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
// Draft: the editable state. Leaves hold raw text (so "-", "1_000", "0x" and
|
|
383
|
+
// half-typed principals never break the tree); an opt keeps its inner draft
|
|
384
|
+
// while toggled off; a variant keeps one payload draft per arm visited; vec
|
|
385
|
+
// items carry stable ids for React keys.
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
export type BytesFormat = "hex" | "utf8" | "base64"
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The editable state of one value: JSON-serializable, mirrors the schema.
|
|
392
|
+
* Leaves hold raw text; an opt keeps its inner draft while toggled off; a
|
|
393
|
+
* variant keeps one payload draft per arm visited; vec items carry stable ids
|
|
394
|
+
* (React keys). A blob's `format` is absent until someone picks one: the
|
|
395
|
+
* editor then shows its own default (text for a memo, hex otherwise), and a
|
|
396
|
+
* format the user picked holds even while the text is empty.
|
|
397
|
+
*/
|
|
398
|
+
export type Draft =
|
|
399
|
+
| { readonly k: "leaf"; readonly text: string }
|
|
400
|
+
| { readonly k: "bool"; readonly value: boolean }
|
|
401
|
+
| { readonly k: "fixed" }
|
|
402
|
+
| {
|
|
403
|
+
readonly k: "bytes"
|
|
404
|
+
readonly text: string
|
|
405
|
+
readonly format?: BytesFormat
|
|
406
|
+
}
|
|
407
|
+
| { readonly k: "opt"; readonly present: boolean; readonly inner?: Draft }
|
|
408
|
+
| {
|
|
409
|
+
readonly k: "vec"
|
|
410
|
+
readonly items: readonly { readonly id: number; readonly draft: Draft }[]
|
|
411
|
+
}
|
|
412
|
+
| { readonly k: "record"; readonly fields: Readonly<Record<string, Draft>> }
|
|
413
|
+
| { readonly k: "tuple"; readonly items: readonly Draft[] }
|
|
414
|
+
| {
|
|
415
|
+
readonly k: "variant"
|
|
416
|
+
readonly tag: string
|
|
417
|
+
readonly payloads: Readonly<Record<string, Draft>>
|
|
418
|
+
}
|
|
419
|
+
| { readonly k: "func"; readonly principal: string; readonly method: string }
|
|
420
|
+
| { readonly k: "uninhabited" }
|
|
421
|
+
|
|
422
|
+
// Vec item ids are React keys only, never rendered into the DOM, so a
|
|
423
|
+
// counter that differs between the server and the browser is harmless.
|
|
424
|
+
let nextItemId = 1
|
|
425
|
+
export const newItemId = (): number => nextItemId++
|
|
426
|
+
|
|
427
|
+
/** A fresh draft for a schema: its default value, as a draft. */
|
|
428
|
+
export function initialDraft(
|
|
429
|
+
schema: AnyFieldSchema,
|
|
430
|
+
opts: DefaultOptions = {}
|
|
431
|
+
): Draft {
|
|
432
|
+
return fromValue(schema, defaultValue(schema, opts), opts, "$", undefined)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const BIG_PRIMITIVES = new Set(["nat", "int", "nat64", "int64"])
|
|
436
|
+
|
|
437
|
+
/** A value `draftFromValue` did not use as given, and what it used instead. */
|
|
438
|
+
export interface DraftNote {
|
|
439
|
+
readonly path: string
|
|
440
|
+
readonly message: string
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface FromValueOptions extends DefaultOptions {
|
|
444
|
+
/**
|
|
445
|
+
* Collects a note wherever the value is not used as given (a misspelled
|
|
446
|
+
* tag, a key the record does not have, a list that is not an array), so a
|
|
447
|
+
* JSON import can say what it replaced rather than stage another call.
|
|
448
|
+
*/
|
|
449
|
+
readonly notes?: DraftNote[]
|
|
450
|
+
/** The value's path, for the notes (`$args[0]`); `$` by default. */
|
|
451
|
+
readonly path?: string
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
type Note = ((path: string, message: string) => void) | undefined
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A draft from any value, tolerantly: a domain value (prefill, history,
|
|
458
|
+
* resendOf) or loose JSON: digit strings for bigints, hex strings or
|
|
459
|
+
* `number[]` for blobs, `{ Tag: payload }` variants, agent-js `[]`/`[v]`
|
|
460
|
+
* opts (boxed ones too), `[principal, method]` func references, objects with
|
|
461
|
+
* `toText()` or `__principal__` for principals. Anything unusable at a node
|
|
462
|
+
* falls back to that node's default, with a note when `notes` is given. A
|
|
463
|
+
* type with no values never gets a draft that holds one: an opt of it stays
|
|
464
|
+
* off, a vec of it empty, and a variant leaves such an arm for its default.
|
|
465
|
+
*/
|
|
466
|
+
export function draftFromValue(
|
|
467
|
+
schema: AnyFieldSchema,
|
|
468
|
+
value: unknown,
|
|
469
|
+
opts: FromValueOptions = {}
|
|
470
|
+
): Draft {
|
|
471
|
+
const { notes, path = "$", ...defaults } = opts
|
|
472
|
+
const note: Note =
|
|
473
|
+
notes === undefined
|
|
474
|
+
? undefined
|
|
475
|
+
: (at, message) => notes.push({ path: at, message })
|
|
476
|
+
return fromValue(schema, value, defaults, path, note)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function fromValue(
|
|
480
|
+
schema: AnyFieldSchema,
|
|
481
|
+
value: unknown,
|
|
482
|
+
opts: DefaultOptions,
|
|
483
|
+
path: string,
|
|
484
|
+
note: Note
|
|
485
|
+
): Draft {
|
|
486
|
+
if (value === UNINHABITED || isUninhabited(schema))
|
|
487
|
+
return { k: "uninhabited" }
|
|
488
|
+
const node = resolve(schema)
|
|
489
|
+
const fallback = (message: string): Draft => {
|
|
490
|
+
note?.(path, message)
|
|
491
|
+
return initialDraft(schema, opts)
|
|
492
|
+
}
|
|
493
|
+
switch (node.kind) {
|
|
494
|
+
case "primitive":
|
|
495
|
+
switch (node.primitive) {
|
|
496
|
+
case "null":
|
|
497
|
+
case "reserved":
|
|
498
|
+
return { k: "fixed" }
|
|
499
|
+
case "bool":
|
|
500
|
+
if (
|
|
501
|
+
typeof value !== "boolean" &&
|
|
502
|
+
value !== "true" &&
|
|
503
|
+
value !== "false"
|
|
504
|
+
)
|
|
505
|
+
note?.(
|
|
506
|
+
path,
|
|
507
|
+
`${missingOr(value, "expected true or false")}; set to false`
|
|
508
|
+
)
|
|
509
|
+
return { k: "bool", value: value === true || value === "true" }
|
|
510
|
+
case "principal":
|
|
511
|
+
return { k: "leaf", text: principalFrom(value, path, note) }
|
|
512
|
+
default:
|
|
513
|
+
return {
|
|
514
|
+
k: "leaf",
|
|
515
|
+
text: leafFrom(node.primitive, value, path, note),
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
case "blob":
|
|
519
|
+
if (isBytes(value)) return bytesDraft(toHex(value))
|
|
520
|
+
if (Array.isArray(value) && value.every(isByte))
|
|
521
|
+
return bytesDraft(toHex(Uint8Array.from(value)))
|
|
522
|
+
if (typeof value === "string") return bytesDraft(value)
|
|
523
|
+
note?.(
|
|
524
|
+
path,
|
|
525
|
+
`${missingOr(value, "expected hex text or a byte array")}; left empty`
|
|
526
|
+
)
|
|
527
|
+
return bytesDraft("")
|
|
528
|
+
case "unit":
|
|
529
|
+
return { k: "fixed" }
|
|
530
|
+
case "opt": {
|
|
531
|
+
if (value === null || value === undefined)
|
|
532
|
+
return { k: "opt", present: false }
|
|
533
|
+
const boxed = isBoxedOpt(node)
|
|
534
|
+
let some: unknown = value
|
|
535
|
+
if (boxed && typeof value === "object" && Object.hasOwn(value, "some")) {
|
|
536
|
+
some = (value as { some: unknown }).some
|
|
537
|
+
} else if (Array.isArray(value) && (boxed || !holdsArray(node.inner))) {
|
|
538
|
+
// agent-js writes an opt as [] or [value]: an unboxed one only when
|
|
539
|
+
// its inner type cannot itself be an array, a boxed one (whose own
|
|
540
|
+
// value is { some } or null, never an array) always.
|
|
541
|
+
if (value.length === 0) return { k: "opt", present: false }
|
|
542
|
+
if (value.length > 1)
|
|
543
|
+
note?.(
|
|
544
|
+
path,
|
|
545
|
+
`expected [] or [value]; the first of ${value.length} values is used`
|
|
546
|
+
)
|
|
547
|
+
some = value[0]
|
|
548
|
+
}
|
|
549
|
+
if (isUninhabited(node.inner)) {
|
|
550
|
+
note?.(path, "this type has no values; left out (null)")
|
|
551
|
+
return { k: "opt", present: false }
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
k: "opt",
|
|
555
|
+
present: true,
|
|
556
|
+
inner: fromValue(
|
|
557
|
+
node.inner,
|
|
558
|
+
some,
|
|
559
|
+
opts,
|
|
560
|
+
boxed ? childPath(path, "some") : path,
|
|
561
|
+
note
|
|
562
|
+
),
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
case "vec": {
|
|
566
|
+
if (!Array.isArray(value)) {
|
|
567
|
+
note?.(
|
|
568
|
+
path,
|
|
569
|
+
`${missingOr(value, "expected a list (an array)")}; left empty`
|
|
570
|
+
)
|
|
571
|
+
return { k: "vec", items: [] }
|
|
572
|
+
}
|
|
573
|
+
if (value.length > 0 && isUninhabited(node.inner)) {
|
|
574
|
+
note?.(
|
|
575
|
+
path,
|
|
576
|
+
`the item type has no values; ${value.length} ${value.length === 1 ? "item" : "items"} left out`
|
|
577
|
+
)
|
|
578
|
+
return { k: "vec", items: [] }
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
k: "vec",
|
|
582
|
+
items: value.map((v, i) => ({
|
|
583
|
+
id: newItemId(),
|
|
584
|
+
draft: fromValue(node.inner, v, opts, childPath(path, i), note),
|
|
585
|
+
})),
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
case "record": {
|
|
589
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
590
|
+
return fallback(
|
|
591
|
+
`${missingOr(value, "expected a record (an object)")}; the default is used`
|
|
592
|
+
)
|
|
593
|
+
const src = value as Record<string, unknown>
|
|
594
|
+
const fields: Record<string, Draft> = {}
|
|
595
|
+
const known = new Set<string>()
|
|
596
|
+
for (const [key, field] of fieldsOf(node)) {
|
|
597
|
+
known.add(key)
|
|
598
|
+
const at = childPath(path, key)
|
|
599
|
+
if (Object.hasOwn(src, key)) {
|
|
600
|
+
setOwn(fields, key, fromValue(field, src[key], opts, at, note))
|
|
601
|
+
} else {
|
|
602
|
+
// A missing opt field is null, as in Candid; anything else is not.
|
|
603
|
+
if (!isOptional(field)) note?.(at, "missing; set to the default")
|
|
604
|
+
setOwn(fields, key, initialDraft(field, opts))
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (note !== undefined)
|
|
608
|
+
for (const key of Object.keys(src))
|
|
609
|
+
if (!known.has(key))
|
|
610
|
+
note(childPath(path, key), "not a field of this record; ignored")
|
|
611
|
+
return { k: "record", fields }
|
|
612
|
+
}
|
|
613
|
+
case "tuple": {
|
|
614
|
+
if (!Array.isArray(value))
|
|
615
|
+
return fallback(
|
|
616
|
+
`${missingOr(value, "expected a tuple (an array)")}; the default is used`
|
|
617
|
+
)
|
|
618
|
+
const elements = elementsOf(node)
|
|
619
|
+
if (value.length > elements.length)
|
|
620
|
+
note?.(
|
|
621
|
+
path,
|
|
622
|
+
`${value.length - elements.length} extra ${value.length - elements.length === 1 ? "value" : "values"} ignored`
|
|
623
|
+
)
|
|
624
|
+
return {
|
|
625
|
+
k: "tuple",
|
|
626
|
+
items: elements.map((el, i) => {
|
|
627
|
+
const at = childPath(path, i)
|
|
628
|
+
if (i < value.length) return fromValue(el, value[i], opts, at, note)
|
|
629
|
+
if (!isOptional(el)) note?.(at, "missing; set to the default")
|
|
630
|
+
return initialDraft(el, opts)
|
|
631
|
+
}),
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
case "variant": {
|
|
635
|
+
const picked = variantOf(node, value)
|
|
636
|
+
if (picked === undefined)
|
|
637
|
+
return fallback(`${variantMiss(value)}; the default arm is used`)
|
|
638
|
+
const [tag, payload, armSchema] = picked
|
|
639
|
+
if (isUninhabited(armSchema))
|
|
640
|
+
return fallback(
|
|
641
|
+
`the arm ${JSON.stringify(tag)} has no values; the default arm is used`
|
|
642
|
+
)
|
|
643
|
+
if (isTagOnly(armSchema)) return { k: "variant", tag, payloads: {} }
|
|
644
|
+
const at = childPath(path, "value")
|
|
645
|
+
if (payload === undefined) note?.(at, "missing; set to the default")
|
|
646
|
+
const payloads: Record<string, Draft> = {}
|
|
647
|
+
setOwn(
|
|
648
|
+
payloads,
|
|
649
|
+
tag,
|
|
650
|
+
payload === undefined
|
|
651
|
+
? initialDraft(armSchema, opts)
|
|
652
|
+
: fromValue(armSchema, payload, opts, at, note)
|
|
653
|
+
)
|
|
654
|
+
return { k: "variant", tag, payloads }
|
|
655
|
+
}
|
|
656
|
+
case "func": {
|
|
657
|
+
// { principal, method }, or agent-js's [principal, method].
|
|
658
|
+
const parts = Array.isArray(value)
|
|
659
|
+
? { principal: value[0], method: value[1] }
|
|
660
|
+
: typeof value === "object" && value !== null
|
|
661
|
+
? (value as { principal?: unknown; method?: unknown })
|
|
662
|
+
: undefined
|
|
663
|
+
if (parts === undefined)
|
|
664
|
+
note?.(
|
|
665
|
+
path,
|
|
666
|
+
`${missingOr(value, "expected { principal, method }")}; left empty`
|
|
667
|
+
)
|
|
668
|
+
else if (typeof parts.method !== "string")
|
|
669
|
+
note?.(
|
|
670
|
+
childPath(path, "method"),
|
|
671
|
+
`${missingOr(parts.method, "expected text")}; left empty`
|
|
672
|
+
)
|
|
673
|
+
return {
|
|
674
|
+
k: "func",
|
|
675
|
+
principal:
|
|
676
|
+
parts === undefined
|
|
677
|
+
? ""
|
|
678
|
+
: principalFrom(
|
|
679
|
+
parts.principal,
|
|
680
|
+
childPath(path, "principal"),
|
|
681
|
+
note
|
|
682
|
+
),
|
|
683
|
+
method: typeof parts?.method === "string" ? parts.method : "",
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
case "service":
|
|
687
|
+
return { k: "leaf", text: principalFrom(value, path, note) }
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** An empty blob has no format yet: the editor shows its default. */
|
|
692
|
+
const bytesDraft = (text: string): Draft =>
|
|
693
|
+
text === "" ? { k: "bytes", text } : { k: "bytes", text, format: "hex" }
|
|
694
|
+
|
|
695
|
+
const missingOr = (value: unknown, expected: string): string =>
|
|
696
|
+
value === null || value === undefined ? "missing" : expected
|
|
697
|
+
|
|
698
|
+
/** The `[tag, payload, armSchema]` of a `{ tag, value }` or `{ Tag: payload }` value. */
|
|
699
|
+
function variantOf(
|
|
700
|
+
node: VariantNode,
|
|
701
|
+
value: unknown
|
|
702
|
+
): [string, unknown, AnyFieldSchema] | undefined {
|
|
703
|
+
if (typeof value !== "object" || value === null) return undefined
|
|
704
|
+
const v = value as Record<string, unknown>
|
|
705
|
+
if (typeof v.tag === "string") {
|
|
706
|
+
const arm = armOf(node, v.tag)
|
|
707
|
+
if (arm !== undefined) return [v.tag, v.value, arm]
|
|
708
|
+
}
|
|
709
|
+
const keys = Object.keys(v)
|
|
710
|
+
const only = keys.length === 1 ? keys[0] : undefined
|
|
711
|
+
if (only !== undefined) {
|
|
712
|
+
const arm = armOf(node, only)
|
|
713
|
+
if (arm !== undefined) return [only, v[only], arm]
|
|
714
|
+
}
|
|
715
|
+
return undefined
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** Why a value picked no arm, for the note. */
|
|
719
|
+
function variantMiss(value: unknown): string {
|
|
720
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
721
|
+
return missingOr(value, `expected a variant ({ "Tag": value })`)
|
|
722
|
+
const v = value as Record<string, unknown>
|
|
723
|
+
if (typeof v.tag === "string")
|
|
724
|
+
return `unknown variant tag ${JSON.stringify(v.tag)}`
|
|
725
|
+
const keys = Object.keys(v)
|
|
726
|
+
return keys.length === 1
|
|
727
|
+
? `unknown variant tag ${JSON.stringify(keys[0])}`
|
|
728
|
+
: `expected one tag, got ${keys.length} keys`
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const isByte = (b: unknown): b is number =>
|
|
732
|
+
typeof b === "number" && Number.isInteger(b) && b >= 0 && b < 256
|
|
733
|
+
|
|
734
|
+
function holdsArray(schema: AnyFieldSchema): boolean {
|
|
735
|
+
const kind = resolve(schema).kind
|
|
736
|
+
return kind === "vec" || kind === "tuple" || kind === "blob" || kind === "opt"
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function leafFrom(
|
|
740
|
+
primitive: string,
|
|
741
|
+
value: unknown,
|
|
742
|
+
path: string,
|
|
743
|
+
note: Note
|
|
744
|
+
): string {
|
|
745
|
+
if (typeof value === "string") return value
|
|
746
|
+
if (typeof value === "bigint") return value.toString()
|
|
747
|
+
if (typeof value === "number") {
|
|
748
|
+
// A JSON number past 2^53 already lost digits: leave the field empty
|
|
749
|
+
// rather than show a different amount.
|
|
750
|
+
if (
|
|
751
|
+
BIG_PRIMITIVES.has(primitive) &&
|
|
752
|
+
Number.isInteger(value) &&
|
|
753
|
+
!Number.isSafeInteger(value)
|
|
754
|
+
) {
|
|
755
|
+
note?.(
|
|
756
|
+
path,
|
|
757
|
+
"a number past 2^53 has lost digits (write it as a string); left empty"
|
|
758
|
+
)
|
|
759
|
+
return ""
|
|
760
|
+
}
|
|
761
|
+
// String(-0) is "0": a float64 -0.0 is other bytes than 0.0.
|
|
762
|
+
return Object.is(value, -0) ? "-0" : String(value)
|
|
763
|
+
}
|
|
764
|
+
note?.(
|
|
765
|
+
path,
|
|
766
|
+
`${missingOr(value, primitive === "text" ? "expected text" : "expected a number")}; left empty`
|
|
767
|
+
)
|
|
768
|
+
return ""
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function principalFrom(value: unknown, path: string, note: Note): string {
|
|
772
|
+
if (typeof value === "string") return value
|
|
773
|
+
if (typeof value === "object" && value !== null) {
|
|
774
|
+
if (typeof (value as { toText?: unknown }).toText === "function")
|
|
775
|
+
return String((value as { toText(): unknown }).toText())
|
|
776
|
+
// agent-js writes a Principal to JSON as { "__principal__": text }.
|
|
777
|
+
const text = getOwn(value as Record<string, unknown>, "__principal__")
|
|
778
|
+
if (typeof text === "string") return text
|
|
779
|
+
}
|
|
780
|
+
note?.(path, `${missingOr(value, "expected principal text")}; left empty`)
|
|
781
|
+
return ""
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** Switching a variant: keeps the draft of every arm already visited. */
|
|
785
|
+
export function selectArm(
|
|
786
|
+
schema: AnyFieldSchema,
|
|
787
|
+
draft: Draft,
|
|
788
|
+
tag: string,
|
|
789
|
+
opts: DefaultOptions = {}
|
|
790
|
+
): Draft {
|
|
791
|
+
const node = resolve(schema)
|
|
792
|
+
if (node.kind !== "variant") return draft
|
|
793
|
+
const arm = armOf(node, tag)
|
|
794
|
+
if (arm === undefined) return draft
|
|
795
|
+
const payloads = draft.k === "variant" ? draft.payloads : {}
|
|
796
|
+
if (isTagOnly(arm) || getOwn(payloads, tag) !== undefined)
|
|
797
|
+
return { k: "variant", tag, payloads }
|
|
798
|
+
return {
|
|
799
|
+
k: "variant",
|
|
800
|
+
tag,
|
|
801
|
+
payloads: withOwn(payloads, tag, initialDraft(arm, opts)),
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ---------------------------------------------------------------------------
|
|
806
|
+
// Draft → value. A parse failure becomes an issue at the leaf's path (code
|
|
807
|
+
// "parse"); the raw text stays in the value as a placeholder so validate()
|
|
808
|
+
// still sees the slot, and the parse message wins for that path.
|
|
809
|
+
// ---------------------------------------------------------------------------
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* An issue at a field. `path` uses the codec's grammar under `$args[i]`
|
|
813
|
+
* (`$args[0].to.owner`), the same paths ic-reactor's `invalid_args` issues
|
|
814
|
+
* carry, so both land on the same field.
|
|
815
|
+
*/
|
|
816
|
+
export interface FieldIssue {
|
|
817
|
+
path: string
|
|
818
|
+
code: string
|
|
819
|
+
message: string
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export function draftToValue(
|
|
823
|
+
schema: AnyFieldSchema,
|
|
824
|
+
draft: Draft,
|
|
825
|
+
path: string,
|
|
826
|
+
issues: FieldIssue[]
|
|
827
|
+
): unknown {
|
|
828
|
+
// Before anything else: an opt, vec item or arm of a type with no values
|
|
829
|
+
// would otherwise stand in for its missing draft with initialDraft(), which
|
|
830
|
+
// is { k: "uninhabited" } again, and recurse forever.
|
|
831
|
+
if (draft.k === "uninhabited" || isUninhabited(schema)) {
|
|
832
|
+
issues.push({
|
|
833
|
+
path,
|
|
834
|
+
code: "uninhabited_type",
|
|
835
|
+
message: "this type has no values",
|
|
836
|
+
})
|
|
837
|
+
return null
|
|
838
|
+
}
|
|
839
|
+
const node = resolve(schema)
|
|
840
|
+
const parse = (message: string): void => {
|
|
841
|
+
issues.push({ path, code: "parse", message })
|
|
842
|
+
}
|
|
843
|
+
switch (node.kind) {
|
|
844
|
+
case "primitive": {
|
|
845
|
+
const p = node.primitive
|
|
846
|
+
if (p === "null" || p === "reserved") return null
|
|
847
|
+
if (p === "bool") return draft.k === "bool" ? draft.value : false
|
|
848
|
+
const text = draft.k === "leaf" ? draft.text : ""
|
|
849
|
+
if (p === "text") return text
|
|
850
|
+
if (p === "principal") return principalOrIssue(text, parse)
|
|
851
|
+
if (BIG_PRIMITIVES.has(p)) {
|
|
852
|
+
const parsed = parseBigInt(text)
|
|
853
|
+
if (typeof parsed === "string") {
|
|
854
|
+
parse(parsed)
|
|
855
|
+
return text
|
|
856
|
+
}
|
|
857
|
+
return parsed
|
|
858
|
+
}
|
|
859
|
+
if (FIXED[p] !== undefined) {
|
|
860
|
+
const t = integerText(text)
|
|
861
|
+
if (!t.ok || t.text.startsWith("0x")) {
|
|
862
|
+
parse(t.ok ? "enter a whole number" : t.message)
|
|
863
|
+
return text
|
|
864
|
+
}
|
|
865
|
+
return Number(t.text) // the range is validate's out_of_range, with exact bounds
|
|
866
|
+
}
|
|
867
|
+
return parseFloatLeaf(text, p === "float32", parse)
|
|
868
|
+
}
|
|
869
|
+
case "blob": {
|
|
870
|
+
if (draft.k !== "bytes") return new Uint8Array(0)
|
|
871
|
+
const bytes = parseBytes(draft.text, draft.format ?? "hex")
|
|
872
|
+
if (typeof bytes === "string") {
|
|
873
|
+
parse(bytes)
|
|
874
|
+
return draft.text
|
|
875
|
+
}
|
|
876
|
+
return bytes
|
|
877
|
+
}
|
|
878
|
+
case "unit":
|
|
879
|
+
return {}
|
|
880
|
+
case "opt": {
|
|
881
|
+
if (draft.k !== "opt" || !draft.present) return null
|
|
882
|
+
const boxed = isBoxedOpt(node)
|
|
883
|
+
const innerDraft = draft.inner ?? initialDraft(node.inner)
|
|
884
|
+
const inner = draftToValue(
|
|
885
|
+
node.inner,
|
|
886
|
+
innerDraft,
|
|
887
|
+
boxed ? childPath(path, "some") : path,
|
|
888
|
+
issues
|
|
889
|
+
)
|
|
890
|
+
return boxed ? { some: inner } : inner
|
|
891
|
+
}
|
|
892
|
+
case "vec":
|
|
893
|
+
return draft.k === "vec"
|
|
894
|
+
? draft.items.map((item, i) =>
|
|
895
|
+
draftToValue(node.inner, item.draft, childPath(path, i), issues)
|
|
896
|
+
)
|
|
897
|
+
: []
|
|
898
|
+
case "record": {
|
|
899
|
+
const out: Record<string, unknown> = {}
|
|
900
|
+
for (const [key, field] of fieldsOf(node)) {
|
|
901
|
+
const sub = draft.k === "record" ? getOwn(draft.fields, key) : undefined
|
|
902
|
+
setOwn(
|
|
903
|
+
out,
|
|
904
|
+
key,
|
|
905
|
+
draftToValue(
|
|
906
|
+
field,
|
|
907
|
+
sub ?? initialDraft(field),
|
|
908
|
+
childPath(path, key),
|
|
909
|
+
issues
|
|
910
|
+
)
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
return out
|
|
914
|
+
}
|
|
915
|
+
case "tuple":
|
|
916
|
+
return elementsOf(node).map((el, i) => {
|
|
917
|
+
const sub = draft.k === "tuple" ? draft.items[i] : undefined
|
|
918
|
+
return draftToValue(
|
|
919
|
+
el,
|
|
920
|
+
sub ?? initialDraft(el),
|
|
921
|
+
childPath(path, i),
|
|
922
|
+
issues
|
|
923
|
+
)
|
|
924
|
+
})
|
|
925
|
+
case "variant": {
|
|
926
|
+
if (draft.k !== "variant") return null
|
|
927
|
+
const armSchema = armOf(node, draft.tag)
|
|
928
|
+
if (armSchema === undefined) {
|
|
929
|
+
issues.push({
|
|
930
|
+
path: childPath(path, "tag"),
|
|
931
|
+
code: "parse",
|
|
932
|
+
message: `unknown variant tag ${JSON.stringify(draft.tag)}`,
|
|
933
|
+
})
|
|
934
|
+
return { tag: draft.tag }
|
|
935
|
+
}
|
|
936
|
+
if (isTagOnly(armSchema)) return { tag: draft.tag }
|
|
937
|
+
const payload =
|
|
938
|
+
getOwn(draft.payloads, draft.tag) ?? initialDraft(armSchema)
|
|
939
|
+
return {
|
|
940
|
+
tag: draft.tag,
|
|
941
|
+
value: draftToValue(
|
|
942
|
+
armSchema,
|
|
943
|
+
payload,
|
|
944
|
+
childPath(path, "value"),
|
|
945
|
+
issues
|
|
946
|
+
),
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
case "func": {
|
|
950
|
+
const f = draft.k === "func" ? draft : { principal: "", method: "" }
|
|
951
|
+
const principal = principalOrIssue(f.principal, (message) =>
|
|
952
|
+
issues.push({
|
|
953
|
+
path: childPath(path, "principal"),
|
|
954
|
+
code: "parse",
|
|
955
|
+
message,
|
|
956
|
+
})
|
|
957
|
+
)
|
|
958
|
+
const method = f.method.trim()
|
|
959
|
+
if (method === "")
|
|
960
|
+
issues.push({
|
|
961
|
+
path: childPath(path, "method"),
|
|
962
|
+
code: "parse",
|
|
963
|
+
message: "required",
|
|
964
|
+
})
|
|
965
|
+
return { principal, method }
|
|
966
|
+
}
|
|
967
|
+
case "service":
|
|
968
|
+
return principalOrIssue(draft.k === "leaf" ? draft.text : "", parse)
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function principalOrIssue(
|
|
973
|
+
text: string,
|
|
974
|
+
report: (message: string) => void
|
|
975
|
+
): string {
|
|
976
|
+
const t = text.trim()
|
|
977
|
+
if (t === "") report("required")
|
|
978
|
+
else if (!isPrincipal(t)) {
|
|
979
|
+
if (isPrincipal(t.toLowerCase())) report("principal text is lower case")
|
|
980
|
+
else report("not a valid principal (wrong grouping or checksum)")
|
|
981
|
+
}
|
|
982
|
+
return t
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Decimal notation only: Number() also reads "0x1A", "0b11" and "", which a
|
|
986
|
+
// float field should not take for a decimal.
|
|
987
|
+
const DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i
|
|
988
|
+
|
|
989
|
+
function parseFloatLeaf(
|
|
990
|
+
text: string,
|
|
991
|
+
float32: boolean,
|
|
992
|
+
parse: (message: string) => void
|
|
993
|
+
): number | string {
|
|
994
|
+
const t = text.trim()
|
|
995
|
+
if (t === "NaN") return NaN
|
|
996
|
+
if (t === "Infinity" || t === "+Infinity") return Infinity
|
|
997
|
+
if (t === "-Infinity") return -Infinity
|
|
998
|
+
if (!DECIMAL.test(t)) {
|
|
999
|
+
parse(t === "" ? "required" : "enter a number")
|
|
1000
|
+
return text
|
|
1001
|
+
}
|
|
1002
|
+
const x = Number(t)
|
|
1003
|
+
if (!Number.isFinite(x)) {
|
|
1004
|
+
// "1e400" overflows to Infinity: say so, never send Infinity for it.
|
|
1005
|
+
parse("too large for a float64 (type Infinity for infinity)")
|
|
1006
|
+
return text
|
|
1007
|
+
}
|
|
1008
|
+
if (float32 && Math.fround(x) !== x) {
|
|
1009
|
+
// encode would refuse it too (unrepresentable_float32): say why, here.
|
|
1010
|
+
parse(`not exactly representable as float32 (nearest is ${Math.fround(x)})`)
|
|
1011
|
+
return x
|
|
1012
|
+
}
|
|
1013
|
+
return x
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
type IntegerText =
|
|
1017
|
+
| { readonly ok: true; readonly text: string }
|
|
1018
|
+
| { readonly ok: false; readonly message: string }
|
|
1019
|
+
|
|
1020
|
+
const GROUP_SPACE = /[ \u00a0\u202f]/g
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Typed integer text as BigInt() reads it, or why not. `_` may sit between
|
|
1024
|
+
* digits (Candid's own `1_000_000`); `,` and spaces only between groups of
|
|
1025
|
+
* three, so a decimal comma ("1,5") is refused rather than read as 15.
|
|
1026
|
+
*/
|
|
1027
|
+
function integerText(text: string): IntegerText {
|
|
1028
|
+
const t = text.trim()
|
|
1029
|
+
if (t === "") return { ok: false, message: "required" }
|
|
1030
|
+
if (/^[+-]?\d+(?:_\d+)*$/.test(t))
|
|
1031
|
+
return { ok: true, text: t.replaceAll("_", "") }
|
|
1032
|
+
if (/^[+-]?\d{1,3}(?:,\d{3})+$/.test(t))
|
|
1033
|
+
return { ok: true, text: t.replaceAll(",", "") }
|
|
1034
|
+
if (/^[+-]?\d{1,3}(?:[ \u00a0\u202f]\d{3})+$/.test(t))
|
|
1035
|
+
return { ok: true, text: t.replace(GROUP_SPACE, "") }
|
|
1036
|
+
if (/^0x[0-9a-f]+(?:_[0-9a-f]+)*$/i.test(t))
|
|
1037
|
+
return { ok: true, text: t.replaceAll("_", "") }
|
|
1038
|
+
if (/^[+-]?[\d,_\s\u00a0\u202f]+$/.test(t))
|
|
1039
|
+
return {
|
|
1040
|
+
ok: false,
|
|
1041
|
+
message:
|
|
1042
|
+
'enter a whole number ("," and spaces only separate groups of 3 digits)',
|
|
1043
|
+
}
|
|
1044
|
+
return { ok: false, message: "enter a whole number" }
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* A bigint from typed text, or why not: decimal digits (with `_` between
|
|
1049
|
+
* them, or `,`/spaces between groups of three) or `0x` hex.
|
|
1050
|
+
*/
|
|
1051
|
+
export function parseBigInt(text: string): bigint | string {
|
|
1052
|
+
const t = integerText(text)
|
|
1053
|
+
return t.ok ? BigInt(t.text) : t.message
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/** Bytes from text in a format, or why not. Hex allows `0x` and separators. */
|
|
1057
|
+
export function parseBytes(
|
|
1058
|
+
text: string,
|
|
1059
|
+
format: BytesFormat
|
|
1060
|
+
): Uint8Array | string {
|
|
1061
|
+
// A copy, so the bytes are this realm's Uint8Array wherever the encoder
|
|
1062
|
+
// comes from (a test's jsdom window has its own).
|
|
1063
|
+
if (format === "utf8") return Uint8Array.from(new TextEncoder().encode(text))
|
|
1064
|
+
if (format === "base64") {
|
|
1065
|
+
try {
|
|
1066
|
+
const bin = atob(text.trim())
|
|
1067
|
+
return Uint8Array.from(bin, (ch) => ch.charCodeAt(0))
|
|
1068
|
+
} catch {
|
|
1069
|
+
return "not valid base64"
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const t = text
|
|
1073
|
+
.trim()
|
|
1074
|
+
.replace(/^0x/i, "")
|
|
1075
|
+
.replace(/[\s:_-]/g, "")
|
|
1076
|
+
if (!/^[0-9a-f]*$/i.test(t)) return "not valid hex"
|
|
1077
|
+
if (t.length % 2 !== 0) return "hex needs an even number of digits"
|
|
1078
|
+
const out = new Uint8Array(t.length / 2)
|
|
1079
|
+
for (let i = 0; i < out.length; i++)
|
|
1080
|
+
out[i] = Number.parseInt(t.slice(i * 2, i * 2 + 2), 16)
|
|
1081
|
+
return out
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Bytes as text in a format, or `undefined` when the format cannot hold
|
|
1086
|
+
* them (utf8 for bytes that are not valid UTF-8).
|
|
1087
|
+
*/
|
|
1088
|
+
export function formatBytes(
|
|
1089
|
+
bytes: Uint8Array,
|
|
1090
|
+
format: BytesFormat
|
|
1091
|
+
): string | undefined {
|
|
1092
|
+
if (format === "hex") return toHex(bytes)
|
|
1093
|
+
if (format === "base64") {
|
|
1094
|
+
let bin = ""
|
|
1095
|
+
for (const b of bytes) bin += String.fromCharCode(b)
|
|
1096
|
+
return btoa(bin)
|
|
1097
|
+
}
|
|
1098
|
+
let text: string
|
|
1099
|
+
try {
|
|
1100
|
+
// ignoreBOM: a leading EF BB BF is three of the bytes, not a marker to
|
|
1101
|
+
// drop, or switching to text would send three bytes fewer.
|
|
1102
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
1103
|
+
bytes
|
|
1104
|
+
)
|
|
1105
|
+
} catch {
|
|
1106
|
+
return undefined
|
|
1107
|
+
}
|
|
1108
|
+
// Only a text that encodes back to the very same bytes may stand for them.
|
|
1109
|
+
const back = new TextEncoder().encode(text)
|
|
1110
|
+
return back.length === bytes.length && back.every((b, i) => b === bytes[i])
|
|
1111
|
+
? text
|
|
1112
|
+
: undefined
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// ---------------------------------------------------------------------------
|
|
1116
|
+
// One check for a whole call: parse → validate (per argument) → encodeArgs.
|
|
1117
|
+
// ---------------------------------------------------------------------------
|
|
1118
|
+
|
|
1119
|
+
/** The result of checking a whole call: parse → validate → encodeArgs. */
|
|
1120
|
+
export interface CallCheck {
|
|
1121
|
+
ok: boolean
|
|
1122
|
+
/** One domain value per argument. */
|
|
1123
|
+
values?: unknown[]
|
|
1124
|
+
/** What ic-reactor's builders take: nothing, the one value, or the tuple. */
|
|
1125
|
+
vars?: unknown
|
|
1126
|
+
/** The exact Candid message (DIDL…) when everything passed. */
|
|
1127
|
+
bytes?: Uint8Array
|
|
1128
|
+
issues: FieldIssue[]
|
|
1129
|
+
/** Some argument has no finite value: the method cannot be called. */
|
|
1130
|
+
uninhabited?: boolean
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/** The values the drafts stand for, parse failures left as their raw text. */
|
|
1134
|
+
export function draftValues(
|
|
1135
|
+
args: readonly AnyFieldSchema[],
|
|
1136
|
+
drafts: readonly (Draft | undefined)[]
|
|
1137
|
+
): unknown[] {
|
|
1138
|
+
return args.map((schema, i) =>
|
|
1139
|
+
draftToValue(schema, drafts[i] ?? initialDraft(schema), argPath(i), [])
|
|
1140
|
+
)
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** Checks a whole call; issues use `$args[i]…` paths. */
|
|
1144
|
+
export function checkCall(
|
|
1145
|
+
args: readonly AnyFieldSchema[],
|
|
1146
|
+
drafts: readonly Draft[]
|
|
1147
|
+
): CallCheck {
|
|
1148
|
+
const empty = args.findIndex(
|
|
1149
|
+
(schema, i) => drafts[i]?.k === "uninhabited" || isUninhabited(schema)
|
|
1150
|
+
)
|
|
1151
|
+
if (empty !== -1) {
|
|
1152
|
+
return {
|
|
1153
|
+
ok: false,
|
|
1154
|
+
uninhabited: true,
|
|
1155
|
+
issues: [
|
|
1156
|
+
{
|
|
1157
|
+
path: argPath(empty),
|
|
1158
|
+
code: "uninhabited_type",
|
|
1159
|
+
message:
|
|
1160
|
+
"this argument's type has no values (empty, or a type that only contains itself): the method cannot be called",
|
|
1161
|
+
},
|
|
1162
|
+
],
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
const parseIssues: FieldIssue[] = []
|
|
1166
|
+
const values = args.map((schema, i) =>
|
|
1167
|
+
draftToValue(
|
|
1168
|
+
schema,
|
|
1169
|
+
drafts[i] ?? initialDraft(schema),
|
|
1170
|
+
argPath(i),
|
|
1171
|
+
parseIssues
|
|
1172
|
+
)
|
|
1173
|
+
)
|
|
1174
|
+
const parsed = new Set(parseIssues.map((issue) => issue.path))
|
|
1175
|
+
const issues: FieldIssue[] = [...parseIssues]
|
|
1176
|
+
args.forEach((schema, i) => {
|
|
1177
|
+
// AnyFieldSchema includes Schema<never>, which validate()'s Schema<T>
|
|
1178
|
+
// parameter refuses at the type level; the node is the same.
|
|
1179
|
+
const result = validate(schema as AnySchema, values[i], { maxIssues: 50 })
|
|
1180
|
+
if (result.ok) return
|
|
1181
|
+
for (const issue of result.issues) {
|
|
1182
|
+
const path = argPath(i, issue.path)
|
|
1183
|
+
if (!parsed.has(path))
|
|
1184
|
+
issues.push({ path, code: issue.code, message: issue.message })
|
|
1185
|
+
}
|
|
1186
|
+
})
|
|
1187
|
+
const vars = toVars(values)
|
|
1188
|
+
if (issues.length > 0) return { ok: false, values, vars, issues }
|
|
1189
|
+
// encode re-validates (stopping at the first issue) and adds what only it
|
|
1190
|
+
// finds: lone surrogates in text, float32 rounding.
|
|
1191
|
+
const encoded = encodeArgs(args, values)
|
|
1192
|
+
if (!encoded.ok)
|
|
1193
|
+
return {
|
|
1194
|
+
ok: false,
|
|
1195
|
+
values,
|
|
1196
|
+
vars,
|
|
1197
|
+
issues: encoded.issues.map(({ path, code, message }) => ({
|
|
1198
|
+
path,
|
|
1199
|
+
code,
|
|
1200
|
+
message,
|
|
1201
|
+
})),
|
|
1202
|
+
}
|
|
1203
|
+
return { ok: true, values, vars, issues: [], bytes: encoded.bytes }
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/** Issues grouped by path, for O(1) lookup from each field component. */
|
|
1207
|
+
export function issuesByPath(
|
|
1208
|
+
issues: readonly FieldIssue[]
|
|
1209
|
+
): Map<string, FieldIssue[]> {
|
|
1210
|
+
const map = new Map<string, FieldIssue[]>()
|
|
1211
|
+
for (const issue of issues) {
|
|
1212
|
+
const list = map.get(issue.path)
|
|
1213
|
+
if (list) list.push(issue)
|
|
1214
|
+
else map.set(issue.path, [issue])
|
|
1215
|
+
}
|
|
1216
|
+
return map
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/** Whether `path` or anything below it has an issue (for collapsed sections). */
|
|
1220
|
+
export function isAtOrBelow(issuePath: string, path: string): boolean {
|
|
1221
|
+
return (
|
|
1222
|
+
issuePath === path ||
|
|
1223
|
+
issuePath.startsWith(path + ".") ||
|
|
1224
|
+
issuePath.startsWith(path + "[")
|
|
1225
|
+
)
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Every path the form renders for a value: what a field component registers.
|
|
1230
|
+
* The tests use it to prove each issue lands on a rendered field.
|
|
1231
|
+
*/
|
|
1232
|
+
export function renderedPaths(
|
|
1233
|
+
schema: AnyFieldSchema,
|
|
1234
|
+
value: unknown,
|
|
1235
|
+
path: string,
|
|
1236
|
+
out: Set<string> = new Set()
|
|
1237
|
+
): Set<string> {
|
|
1238
|
+
out.add(path)
|
|
1239
|
+
const node = resolve(schema)
|
|
1240
|
+
switch (node.kind) {
|
|
1241
|
+
case "opt":
|
|
1242
|
+
if (value !== null && value !== undefined) {
|
|
1243
|
+
const boxed = isBoxedOpt(node)
|
|
1244
|
+
renderedPaths(
|
|
1245
|
+
node.inner,
|
|
1246
|
+
boxed ? (value as { some: unknown }).some : value,
|
|
1247
|
+
boxed ? childPath(path, "some") : path,
|
|
1248
|
+
out
|
|
1249
|
+
)
|
|
1250
|
+
}
|
|
1251
|
+
break
|
|
1252
|
+
case "vec":
|
|
1253
|
+
if (Array.isArray(value))
|
|
1254
|
+
value.forEach((v, i) =>
|
|
1255
|
+
renderedPaths(node.inner, v, childPath(path, i), out)
|
|
1256
|
+
)
|
|
1257
|
+
break
|
|
1258
|
+
case "record":
|
|
1259
|
+
for (const [key, field] of fieldsOf(node)) {
|
|
1260
|
+
const v =
|
|
1261
|
+
typeof value === "object" && value !== null
|
|
1262
|
+
? getOwn(value as Record<string, unknown>, key)
|
|
1263
|
+
: undefined
|
|
1264
|
+
renderedPaths(field, v, childPath(path, key), out)
|
|
1265
|
+
}
|
|
1266
|
+
break
|
|
1267
|
+
case "tuple":
|
|
1268
|
+
elementsOf(node).forEach((el, i) =>
|
|
1269
|
+
renderedPaths(
|
|
1270
|
+
el,
|
|
1271
|
+
Array.isArray(value) ? value[i] : undefined,
|
|
1272
|
+
childPath(path, i),
|
|
1273
|
+
out
|
|
1274
|
+
)
|
|
1275
|
+
)
|
|
1276
|
+
break
|
|
1277
|
+
case "variant": {
|
|
1278
|
+
out.add(childPath(path, "tag"))
|
|
1279
|
+
const v = value as { tag?: unknown; value?: unknown } | null
|
|
1280
|
+
if (v !== null && typeof v === "object" && typeof v.tag === "string") {
|
|
1281
|
+
const arm = armOf(node, v.tag)
|
|
1282
|
+
if (arm !== undefined && "value" in v)
|
|
1283
|
+
renderedPaths(arm, v.value, childPath(path, "value"), out)
|
|
1284
|
+
}
|
|
1285
|
+
break
|
|
1286
|
+
}
|
|
1287
|
+
case "func":
|
|
1288
|
+
out.add(childPath(path, "principal"))
|
|
1289
|
+
out.add(childPath(path, "method"))
|
|
1290
|
+
break
|
|
1291
|
+
default:
|
|
1292
|
+
break
|
|
1293
|
+
}
|
|
1294
|
+
return out
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// ---------------------------------------------------------------------------
|
|
1298
|
+
// Walking drafts (for "now" defaults): visit every node of the drafts with
|
|
1299
|
+
// its schema, path and record key; a visitor that returns a draft replaces
|
|
1300
|
+
// that node and is not called below it.
|
|
1301
|
+
// ---------------------------------------------------------------------------
|
|
1302
|
+
|
|
1303
|
+
export interface DraftVisit {
|
|
1304
|
+
readonly schema: AnyFieldSchema
|
|
1305
|
+
readonly draft: Draft
|
|
1306
|
+
readonly path: string
|
|
1307
|
+
/** The record key of the field, when the node is one. */
|
|
1308
|
+
readonly key?: string
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
export function mapDrafts(
|
|
1312
|
+
args: readonly AnyFieldSchema[],
|
|
1313
|
+
drafts: readonly Draft[],
|
|
1314
|
+
visit: (at: DraftVisit) => Draft | undefined
|
|
1315
|
+
): Draft[] {
|
|
1316
|
+
return args.map((schema, i) => {
|
|
1317
|
+
const draft = drafts[i] ?? initialDraft(schema)
|
|
1318
|
+
return mapDraft({ schema, draft, path: argPath(i) }, visit)
|
|
1319
|
+
})
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function mapDraft(
|
|
1323
|
+
at: DraftVisit,
|
|
1324
|
+
visit: (at: DraftVisit) => Draft | undefined
|
|
1325
|
+
): Draft {
|
|
1326
|
+
const replaced = visit(at)
|
|
1327
|
+
if (replaced !== undefined) return replaced
|
|
1328
|
+
const { schema, draft, path, key } = at
|
|
1329
|
+
const node = resolve(schema)
|
|
1330
|
+
switch (node.kind) {
|
|
1331
|
+
case "opt": {
|
|
1332
|
+
if (draft.k !== "opt" || !draft.present || draft.inner === undefined)
|
|
1333
|
+
return draft
|
|
1334
|
+
const boxed = isBoxedOpt(node)
|
|
1335
|
+
const inner = mapDraft(
|
|
1336
|
+
{
|
|
1337
|
+
schema: node.inner,
|
|
1338
|
+
draft: draft.inner,
|
|
1339
|
+
path: boxed ? childPath(path, "some") : path,
|
|
1340
|
+
key: boxed ? undefined : key,
|
|
1341
|
+
},
|
|
1342
|
+
visit
|
|
1343
|
+
)
|
|
1344
|
+
return inner === draft.inner ? draft : { ...draft, inner }
|
|
1345
|
+
}
|
|
1346
|
+
case "record": {
|
|
1347
|
+
if (draft.k !== "record") return draft
|
|
1348
|
+
let changed = false
|
|
1349
|
+
const fields: Record<string, Draft> = {}
|
|
1350
|
+
for (const [k, field] of fieldsOf(node)) {
|
|
1351
|
+
const sub = getOwn(draft.fields, k)
|
|
1352
|
+
if (sub === undefined) continue
|
|
1353
|
+
const next = mapDraft(
|
|
1354
|
+
{ schema: field, draft: sub, path: childPath(path, k), key: k },
|
|
1355
|
+
visit
|
|
1356
|
+
)
|
|
1357
|
+
if (next !== sub) changed = true
|
|
1358
|
+
setOwn(fields, k, next)
|
|
1359
|
+
}
|
|
1360
|
+
return changed ? { k: "record", fields } : draft
|
|
1361
|
+
}
|
|
1362
|
+
case "tuple": {
|
|
1363
|
+
if (draft.k !== "tuple") return draft
|
|
1364
|
+
const elements = elementsOf(node)
|
|
1365
|
+
const items = draft.items.map((sub, i) => {
|
|
1366
|
+
const el = elements[i]
|
|
1367
|
+
return el === undefined
|
|
1368
|
+
? sub
|
|
1369
|
+
: mapDraft(
|
|
1370
|
+
{ schema: el, draft: sub, path: childPath(path, i) },
|
|
1371
|
+
visit
|
|
1372
|
+
)
|
|
1373
|
+
})
|
|
1374
|
+
return items.every((d, i) => d === draft.items[i])
|
|
1375
|
+
? draft
|
|
1376
|
+
: { k: "tuple", items }
|
|
1377
|
+
}
|
|
1378
|
+
case "vec": {
|
|
1379
|
+
if (draft.k !== "vec") return draft
|
|
1380
|
+
let changed = false
|
|
1381
|
+
const items = draft.items.map((item, i) => {
|
|
1382
|
+
const next = mapDraft(
|
|
1383
|
+
{ schema: node.inner, draft: item.draft, path: childPath(path, i) },
|
|
1384
|
+
visit
|
|
1385
|
+
)
|
|
1386
|
+
if (next === item.draft) return item
|
|
1387
|
+
changed = true
|
|
1388
|
+
// The same id: the item keeps its React identity and its focus.
|
|
1389
|
+
return { id: item.id, draft: next }
|
|
1390
|
+
})
|
|
1391
|
+
return changed ? { k: "vec", items } : draft
|
|
1392
|
+
}
|
|
1393
|
+
case "variant": {
|
|
1394
|
+
if (draft.k !== "variant") return draft
|
|
1395
|
+
const arm = armOf(node, draft.tag)
|
|
1396
|
+
const payload = getOwn(draft.payloads, draft.tag)
|
|
1397
|
+
if (arm === undefined || payload === undefined) return draft
|
|
1398
|
+
const next = mapDraft(
|
|
1399
|
+
{ schema: arm, draft: payload, path: childPath(path, "value") },
|
|
1400
|
+
visit
|
|
1401
|
+
)
|
|
1402
|
+
return next === payload
|
|
1403
|
+
? draft
|
|
1404
|
+
: { ...draft, payloads: withOwn(draft.payloads, draft.tag, next) }
|
|
1405
|
+
}
|
|
1406
|
+
default:
|
|
1407
|
+
return draft
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* The draft set to a time in nanoseconds: a bigint leaf takes it, an opt is
|
|
1413
|
+
* switched on and filled, a one-field record (the ICP ledger's
|
|
1414
|
+
* `TimeStamp { timestamp_nanos }`) fills its field. Other nodes stay as they are.
|
|
1415
|
+
*/
|
|
1416
|
+
export function fillNanos(
|
|
1417
|
+
schema: AnyFieldSchema,
|
|
1418
|
+
draft: Draft,
|
|
1419
|
+
nanos: bigint
|
|
1420
|
+
): Draft {
|
|
1421
|
+
const node = resolve(schema)
|
|
1422
|
+
switch (node.kind) {
|
|
1423
|
+
case "primitive":
|
|
1424
|
+
return BIG_PRIMITIVES.has(node.primitive)
|
|
1425
|
+
? { k: "leaf", text: nanos.toString() }
|
|
1426
|
+
: draft
|
|
1427
|
+
case "opt": {
|
|
1428
|
+
const inner =
|
|
1429
|
+
draft.k === "opt" && draft.inner !== undefined
|
|
1430
|
+
? draft.inner
|
|
1431
|
+
: initialDraft(node.inner)
|
|
1432
|
+
return {
|
|
1433
|
+
k: "opt",
|
|
1434
|
+
present: true,
|
|
1435
|
+
inner: fillNanos(node.inner, inner, nanos),
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
case "record": {
|
|
1439
|
+
const fields = fieldsOf(node)
|
|
1440
|
+
const only = fields.length === 1 ? fields[0] : undefined
|
|
1441
|
+
if (only === undefined) return draft
|
|
1442
|
+
const [key, field] = only
|
|
1443
|
+
const current =
|
|
1444
|
+
draft.k === "record" ? getOwn(draft.fields, key) : undefined
|
|
1445
|
+
const out: Record<string, Draft> = {}
|
|
1446
|
+
setOwn(out, key, fillNanos(field, current ?? initialDraft(field), nanos))
|
|
1447
|
+
return { k: "record", fields: out }
|
|
1448
|
+
}
|
|
1449
|
+
default:
|
|
1450
|
+
return draft
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/** Whether a draft holds no time yet (an absent opt, an empty or zero leaf). */
|
|
1455
|
+
export function isUnsetTime(draft: Draft): boolean {
|
|
1456
|
+
if (draft.k === "opt") return !draft.present
|
|
1457
|
+
if (draft.k === "leaf") return draft.text.trim() === "" || draft.text === "0"
|
|
1458
|
+
return false
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// ---------------------------------------------------------------------------
|
|
1462
|
+
// Errors from a call
|
|
1463
|
+
// ---------------------------------------------------------------------------
|
|
1464
|
+
|
|
1465
|
+
interface IssueLike {
|
|
1466
|
+
path: unknown
|
|
1467
|
+
code: unknown
|
|
1468
|
+
message: unknown
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const isIssue = (issue: unknown): issue is FieldIssue =>
|
|
1472
|
+
typeof issue === "object" &&
|
|
1473
|
+
issue !== null &&
|
|
1474
|
+
typeof (issue as IssueLike).path === "string" &&
|
|
1475
|
+
typeof (issue as IssueLike).code === "string" &&
|
|
1476
|
+
typeof (issue as IssueLike).message === "string"
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* An `invalid_args` ReactorError's issues as field issues. ic-reactor takes
|
|
1480
|
+
* them straight from encodeArgs, so their paths are already `$args[i]…`, the
|
|
1481
|
+
* grammar the form's fields use. Any other error gives none. The check is on
|
|
1482
|
+
* the shape, not the brand, so a recorded error (a plain object with `kind`
|
|
1483
|
+
* and `issues`) is read too.
|
|
1484
|
+
*/
|
|
1485
|
+
export function issuesFromReactorError(e: unknown): FieldIssue[] {
|
|
1486
|
+
if (typeof e !== "object" || e === null) return []
|
|
1487
|
+
const error = e as { kind?: unknown; issues?: unknown }
|
|
1488
|
+
if (error.kind !== "invalid_args" || !Array.isArray(error.issues)) return []
|
|
1489
|
+
return error.issues
|
|
1490
|
+
.filter(isIssue)
|
|
1491
|
+
.map(({ path, code, message }) => ({ path, code, message }))
|
|
1492
|
+
}
|