saeeol 1.2.9 → 1.3.1
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/.turbo/turbo-typecheck.log +1 -0
- package/AGENTS.md +72 -0
- package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
- package/Dockerfile +18 -0
- package/assets/saeeol.ico +0 -0
- package/bin/saeeol.cjs +0 -0
- package/database.db +0 -0
- package/drizzle.config.ts +10 -0
- package/git +0 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
- package/migration/20260211171708_add_project_commands/migration.sql +1 -0
- package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
- package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
- package/migration/20260225215848_workspace/migration.sql +7 -0
- package/migration/20260225215848_workspace/snapshot.json +959 -0
- package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
- package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
- package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
- package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
- package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
- package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
- package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
- package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
- package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
- package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
- package/migration/20260323234822_events/migration.sql +13 -0
- package/migration/20260323234822_events/snapshot.json +1271 -0
- package/migration/20260410174513_workspace-name/migration.sql +16 -0
- package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
- package/migration/20260413175956_chief_energizer/migration.sql +13 -0
- package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
- package/migration/20260423070820_add_icon_url_override/migration.sql +2 -0
- package/migration/20260423070820_add_icon_url_override/snapshot.json +1409 -0
- package/migration/20260428004200_add_session_path/migration.sql +1 -0
- package/migration/20260428004200_add_session_path/snapshot.json +1419 -0
- package/npm/bin/saeeol +42 -0
- package/npm/package.json +39 -0
- package/npm/postinstall.js +162 -0
- package/package.json +201 -207
- package/parsers-config.ts +289 -0
- package/script/build.ts +393 -0
- package/script/check-migrations.ts +16 -0
- package/script/fix-node-pty.ts +34 -0
- package/script/generate.ts +23 -0
- package/script/postinstall.mjs +189 -0
- package/script/publish.ts +200 -0
- package/script/run-workspace-server +106 -0
- package/script/schema.ts +63 -0
- package/script/test-runner.ts +420 -0
- package/script/time.ts +6 -0
- package/script/trace-imports.ts +153 -0
- package/script/upgrade-opentui.ts +64 -0
- package/scripts/diff-sdk-types.sh +52 -0
- package/specs/effect/facades.md +221 -0
- package/specs/effect/http-api.md +401 -0
- package/specs/effect/instance-context.md +309 -0
- package/specs/effect/loose-ends.md +34 -0
- package/specs/effect/migration.md +299 -0
- package/specs/effect/routes.md +64 -0
- package/specs/effect/schema.md +399 -0
- package/specs/effect/server-package.md +668 -0
- package/specs/effect/tools.md +90 -0
- package/specs/tui-plugins.md +433 -0
- package/specs/v2/api.ts +67 -0
- package/specs/v2/keymappings.md +10 -0
- package/specs/v2/message-shape.md +136 -0
- package/src/acp/agent-message.ts +1 -1
- package/src/acp/agent-utils.ts +1 -1
- package/src/boxes/ansi.ts +17 -0
- package/src/boxes/atomic-write.ts +35 -0
- package/src/boxes/b64.ts +58 -0
- package/src/boxes/bash-security.ts +129 -0
- package/src/boxes/bom.ts +18 -0
- package/src/boxes/cancel.ts +16 -0
- package/src/boxes/chop.ts +12 -0
- package/src/boxes/clamp.ts +3 -0
- package/src/boxes/compact.ts +9 -0
- package/src/boxes/cost-tracker.ts +116 -0
- package/src/boxes/dataurl.ts +29 -0
- package/src/boxes/delay.ts +27 -0
- package/src/boxes/diff-apply.ts +53 -0
- package/src/boxes/disposable.ts +13 -0
- package/src/boxes/err.ts +34 -0
- package/src/boxes/human.ts +47 -0
- package/src/boxes/iife.ts +9 -0
- package/src/boxes/latch.ts +8 -0
- package/src/boxes/memory.ts +198 -0
- package/src/boxes/net.ts +16 -0
- package/src/boxes/plural.ts +4 -0
- package/src/boxes/puny.ts +21 -0
- package/src/boxes/retry.ts +49 -0
- package/src/boxes/rwlock.ts +41 -0
- package/src/boxes/schedule.ts +71 -0
- package/src/boxes/scope.ts +21 -0
- package/src/boxes/tokens.ts +9 -0
- package/src/boxes/ttl-cache.ts +63 -0
- package/src/boxes/typed-event.ts +51 -0
- package/src/boxes/uid.ts +50 -0
- package/src/boxes/wave6.test.ts +296 -0
- package/src/boxes/wildcard.ts +58 -0
- package/src/bus/global.ts +1 -1
- package/src/cli/cmd/github-run-api.ts +2 -2
- package/src/cli/cmd/run-events.ts +2 -2
- package/src/cli/cmd/tui/component/logo.tsx +1 -1
- package/src/cli/cmd/tui/component/prompt/use-prompt-memos.ts +2 -2
- package/src/cli/cmd/tui/context/app/editor-zed.ts +1 -1
- package/src/cli/cmd/tui/context/app/editor.ts +1 -1
- package/src/cli/cmd/tui/context/app/helper.tsx +1 -0
- package/src/cli/cmd/tui/context/app/theme.tsx +1 -0
- package/src/cli/cmd/tui/util/revert-diff.ts +1 -1
- package/src/overlay/cli/cmd/roll-call-call.ts +1 -1
- package/src/overlay/cost-tracker/format.ts +1 -1
- package/src/overlay/cost-tracker/index.ts +4 -4
- package/src/overlay/cost-tracker/state.ts +2 -2
- package/src/overlay/cost-tracker/types.ts +2 -2
- package/src/overlay/memory/age.ts +1 -1
- package/src/overlay/memory/index.ts +4 -4
- package/src/overlay/memory/paths.ts +2 -2
- package/src/overlay/memory/scan.ts +1 -1
- package/src/overlay/memory/types.ts +2 -2
- package/src/overlay/tool/bash-security.ts +3 -3
- package/src/overlay/util/url.ts +1 -1
- package/src/plugin/codex-auth.ts +1 -1
- package/src/provider/model-cache.ts +2 -2
- package/src/provider/provider-resolve.ts +3 -3
- package/src/provider/transform-message.ts +1 -1
- package/src/server/routes/game.ts +284 -0
- package/src/server/server.ts +2 -0
- package/src/session/core/compaction/compaction-helpers.ts +1 -1
- package/src/session/core/compaction/compaction.ts +1 -1
- package/src/session/core/session-events.ts +50 -8
- package/src/session/core/session.ts +2 -0
- package/src/sessions/ingest-queue.ts +2 -2
- package/src/sessions/remote-ws.ts +1 -1
- package/src/tool/workflow/question.ts +1 -1
- package/src/util/abort.ts +1 -1
- package/src/util/bom.ts +2 -2
- package/src/util/color.ts +1 -1
- package/src/util/data-url.ts +1 -1
- package/src/util/defer.ts +1 -1
- package/src/util/error.ts +2 -2
- package/src/util/filesystem.ts +2 -2
- package/src/util/format.ts +1 -1
- package/src/util/iife.ts +1 -1
- package/src/util/local-context.ts +1 -1
- package/src/util/locale.ts +2 -2
- package/src/util/lock.ts +1 -1
- package/src/util/network.ts +1 -1
- package/src/util/signal.ts +1 -1
- package/src/util/token.ts +1 -1
- package/src/util/wildcard.ts +1 -1
- package/sst-env.d.ts +10 -0
- package/test/AGENTS.md +133 -0
- package/test/account/repo.test.ts +352 -0
- package/test/account/service.test.ts +456 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +725 -0
- package/test/agent/agent.test.ts +890 -0
- package/test/auth/auth.test.ts +86 -0
- package/test/bun/registry.test.ts +75 -0
- package/test/bus/bus-effect.test.ts +161 -0
- package/test/bus/bus-integration.test.ts +87 -0
- package/test/bus/bus.test.ts +219 -0
- package/test/cli/account.test.ts +26 -0
- package/test/cli/auto-mode.test.ts +75 -0
- package/test/cli/bin-saeeol.test.ts +8 -0
- package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
- package/test/cli/cmd/tui/prompt-traits.test.ts +38 -0
- package/test/cli/cmd/tui/sync.test.tsx +159 -0
- package/test/cli/error.test.ts +18 -0
- package/test/cli/github-action.test.ts +198 -0
- package/test/cli/github-remote.test.ts +85 -0
- package/test/cli/import.test.ts +97 -0
- package/test/cli/install-artifact.test.ts +72 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/cli/pr.test.ts +59 -0
- package/test/cli/tui/editor-context-zed.test.ts +356 -0
- package/test/cli/tui/editor-context.test.tsx +228 -0
- package/test/cli/tui/keybind-plugin.test.ts +90 -0
- package/test/cli/tui/markdown.test.ts +161 -0
- package/test/cli/tui/plugin-add.test.ts +111 -0
- package/test/cli/tui/plugin-install.test.ts +87 -0
- package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
- package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
- package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
- package/test/cli/tui/plugin-loader.test.ts +816 -0
- package/test/cli/tui/plugin-toggle.test.ts +157 -0
- package/test/cli/tui/revert-diff.test.ts +35 -0
- package/test/cli/tui/slot-replace.test.tsx +47 -0
- package/test/cli/tui/theme-store.test.ts +54 -0
- package/test/cli/tui/thread.test.ts +28 -0
- package/test/cli/tui/transcript.test.ts +426 -0
- package/test/cli/tui/usage.test.ts +60 -0
- package/test/cli/tui/use-event.test.tsx +175 -0
- package/test/config/agent-color.test.ts +67 -0
- package/test/config/config.test.ts +2544 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/markdown-header.md +11 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/fixtures/weird-model-id.md +13 -0
- package/test/config/lsp.test.ts +87 -0
- package/test/config/markdown.test.ts +228 -0
- package/test/config/plugin.test.ts +0 -0
- package/test/config/tui.test.ts +624 -0
- package/test/control-plane/adapters.test.ts +71 -0
- package/test/control-plane/workspace.test.ts +1526 -0
- package/test/effect/app-runtime-logger.test.ts +98 -0
- package/test/effect/config-service.test.ts +65 -0
- package/test/effect/instance-state.test.ts +394 -0
- package/test/effect/run-service.test.ts +89 -0
- package/test/effect/runner.test.ts +523 -0
- package/test/fake/provider.ts +82 -0
- package/test/file/fsmonitor.test.ts +68 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/index.test.ts +954 -0
- package/test/file/path-traversal.test.ts +205 -0
- package/test/file/ripgrep.test.ts +226 -0
- package/test/file/watcher.test.ts +249 -0
- package/test/filesystem/filesystem.test.ts +319 -0
- package/test/fixture/db.ts +11 -0
- package/test/fixture/fixture.test.ts +26 -0
- package/test/fixture/fixture.ts +175 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/log-init-worker.ts +62 -0
- package/test/fixture/lsp/fake-lsp-server.js +249 -0
- package/test/fixture/plug-worker.ts +93 -0
- package/test/fixture/plugin-meta-worker.ts +19 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
- package/test/fixture/skills/cloudflare/SKILL.md +211 -0
- package/test/fixture/skills/index.json +6 -0
- package/test/fixture/tui-plugin.ts +323 -0
- package/test/fixture/tui-runtime.ts +31 -0
- package/test/format/format.test.ts +272 -0
- package/test/git/git.test.ts +128 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/installation/installation.test.ts +168 -0
- package/test/keybind.test.ts +421 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/filesystem.ts +10 -0
- package/test/lib/llm-server.ts +778 -0
- package/test/lib/websocket.ts +46 -0
- package/test/lsp/client.test.ts +482 -0
- package/test/lsp/index.test.ts +160 -0
- package/test/lsp/launch.test.ts +22 -0
- package/test/lsp/lifecycle.test.ts +184 -0
- package/test/ltm/ltm.test.ts +230 -0
- package/test/mcp/headers.test.ts +178 -0
- package/test/mcp/lifecycle.test.ts +787 -0
- package/test/mcp/oauth-auto-connect.test.ts +311 -0
- package/test/mcp/oauth-browser.test.ts +276 -0
- package/test/mcp/oauth-callback.test.ts +34 -0
- package/test/memory/abort-leak-webfetch.ts +49 -0
- package/test/memory/abort-leak.test.ts +128 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +1227 -0
- package/test/permission/next.toConfig.test.ts +110 -0
- package/test/permission-task.test.ts +326 -0
- package/test/plugin/auth-override.test.ts +79 -0
- package/test/plugin/cloudflare.test.ts +68 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/plugin/github-copilot-models.test.ts +261 -0
- package/test/plugin/install-concurrency.test.ts +140 -0
- package/test/plugin/install.test.ts +570 -0
- package/test/plugin/loader-shared.test.ts +1169 -0
- package/test/plugin/meta.test.ts +137 -0
- package/test/plugin/plugin-contract.test.ts +291 -0
- package/test/plugin/shared.test.ts +88 -0
- package/test/plugin/trigger.test.ts +102 -0
- package/test/plugin/workspace-adapter.test.ts +109 -0
- package/test/preload.ts +77 -0
- package/test/project/instance.test.ts +276 -0
- package/test/project/migrate-global.test.ts +152 -0
- package/test/project/project.test.ts +600 -0
- package/test/project/vcs.test.ts +286 -0
- package/test/project/worktree-remove.test.ts +126 -0
- package/test/project/worktree.test.ts +223 -0
- package/test/provider/amazon-bedrock.test.ts +462 -0
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
- package/test/provider/gitlab-duo.test.ts +413 -0
- package/test/provider/local.test.ts +208 -0
- package/test/provider/models.test.ts +261 -0
- package/test/provider/provider-category.test.ts +190 -0
- package/test/provider/provider.test.ts +2758 -0
- package/test/provider/transform.test.ts +3681 -0
- package/test/pty/pty-output-isolation.test.ts +147 -0
- package/test/pty/pty-session.test.ts +102 -0
- package/test/pty/pty-shell.test.ts +104 -0
- package/test/question/question.test.ts +490 -0
- package/test/saeeol/agent-global-config-dirs.test.ts +24 -0
- package/test/saeeol/agent-manager-tool.test.ts +71 -0
- package/test/saeeol/agent-permission-overrides.test.ts +75 -0
- package/test/saeeol/agent-skill-permissions.test.ts +37 -0
- package/test/saeeol/ask-agent-permissions.test.ts +303 -0
- package/test/saeeol/bash-hierarchy.test.ts +64 -0
- package/test/saeeol/bash-permission-metadata.test.ts +66 -0
- package/test/saeeol/bash-security-extended.test.ts +243 -0
- package/test/saeeol/bedrock-claude-empty-content.test.ts +138 -0
- package/test/saeeol/boxes-integration.test.ts +415 -0
- package/test/saeeol/builtin-skills.test.ts +75 -0
- package/test/saeeol/cleanup.ts +28 -0
- package/test/saeeol/cli/dev-setup.test.ts +74 -0
- package/test/saeeol/cli/roll-call.test.ts +161 -0
- package/test/saeeol/cli-run-auto-helper.test.ts +58 -0
- package/test/saeeol/codex-auth-refresh.test.ts +124 -0
- package/test/saeeol/commit-message/generate.test.ts +188 -0
- package/test/saeeol/commit-message/git-context.test.ts +303 -0
- package/test/saeeol/commit-message-windows.test.ts +38 -0
- package/test/saeeol/compaction-payload-recovery.test.ts +406 -0
- package/test/saeeol/compaction-preservation-audit.test.ts +122 -0
- package/test/saeeol/compaction-skip-guard.test.ts +224 -0
- package/test/saeeol/compaction-smart-select.test.ts +100 -0
- package/test/saeeol/config/config.test.ts +166 -0
- package/test/saeeol/config/indexing-default-plugin.test.ts +82 -0
- package/test/saeeol/config/opentelemetry-default.test.ts +29 -0
- package/test/saeeol/config-gitignore.test.ts +70 -0
- package/test/saeeol/config-injector.test.ts +305 -0
- package/test/saeeol/config-resilience.test.ts +234 -0
- package/test/saeeol/config-validation.test.ts +183 -0
- package/test/saeeol/cost-propagation.test.ts +94 -0
- package/test/saeeol/cost-tracker-extended.test.ts +141 -0
- package/test/saeeol/cost-tracker.test.ts +64 -0
- package/test/saeeol/custom-provider-delete.test.ts +149 -0
- package/test/saeeol/diff-full.test.ts +226 -0
- package/test/saeeol/edit-permission-filediff.test.ts +223 -0
- package/test/saeeol/encoding.test.ts +364 -0
- package/test/saeeol/enhance-prompt.test.ts +61 -0
- package/test/saeeol/ensure-plan-dir.test.ts +32 -0
- package/test/saeeol/errors.test.ts +144 -0
- package/test/saeeol/external-directory-boundary.test.ts +96 -0
- package/test/saeeol/gateway-headers.test.ts +88 -0
- package/test/saeeol/help.test.ts +191 -0
- package/test/saeeol/ignore-migrator.test.ts +308 -0
- package/test/saeeol/indexing-auth.test.ts +45 -0
- package/test/saeeol/indexing-feature.test.ts +44 -0
- package/test/saeeol/indexing-label.test.ts +70 -0
- package/test/saeeol/indexing-startup.test.ts +381 -0
- package/test/saeeol/indexing-worktree.test.ts +73 -0
- package/test/saeeol/instruction.test.ts +136 -0
- package/test/saeeol/lancedb-runtime.test.ts +116 -0
- package/test/saeeol/loader-auth.test.ts +168 -0
- package/test/saeeol/local-model.test.ts +621 -0
- package/test/saeeol/logo.test.ts +31 -0
- package/test/saeeol/lsp-typescript-lightweight.test.ts +89 -0
- package/test/saeeol/mcp-branding.test.ts +33 -0
- package/test/saeeol/mcp-docker-rm.test.ts +32 -0
- package/test/saeeol/mcp-migrator.test.ts +736 -0
- package/test/saeeol/mcp-oauth-callback.test.ts +33 -0
- package/test/saeeol/memory-io.test.ts +198 -0
- package/test/saeeol/memory-paths.test.ts +87 -0
- package/test/saeeol/memory-security.test.ts +166 -0
- package/test/saeeol/model-cache-org.test.ts +164 -0
- package/test/saeeol/model-info-panel-utils.test.ts +52 -0
- package/test/saeeol/model-info-panel.types.test.ts +7 -0
- package/test/saeeol/models-401-fallback.test.ts +52 -0
- package/test/saeeol/modes-migrator.test.ts +320 -0
- package/test/saeeol/nvidia-headers.test.ts +74 -0
- package/test/saeeol/patch-jsonc.test.ts +73 -0
- package/test/saeeol/patch.test.ts +172 -0
- package/test/saeeol/paths.test.ts +265 -0
- package/test/saeeol/permission/config-paths.test.ts +174 -0
- package/test/saeeol/permission/env-read.test.ts +149 -0
- package/test/saeeol/permission/external-directory-allow.test.ts +327 -0
- package/test/saeeol/permission/next.always-rules.test.ts +882 -0
- package/test/saeeol/permission/next.reply-http.test.ts +205 -0
- package/test/saeeol/permission/next.reply-routing.test.ts +184 -0
- package/test/saeeol/plan-exit-detection.test.ts +494 -0
- package/test/saeeol/plan-followup.test.ts +1376 -0
- package/test/saeeol/project-config-update.test.ts +120 -0
- package/test/saeeol/project-id.test.ts +455 -0
- package/test/saeeol/provider-cost.test.ts +171 -0
- package/test/saeeol/provider-list-failed-state.test.ts +100 -0
- package/test/saeeol/question-dismiss-all.test.ts +174 -0
- package/test/saeeol/read-directory.test.ts +116 -0
- package/test/saeeol/rules-migrator.test.ts +257 -0
- package/test/saeeol/run-auto.test.ts +176 -0
- package/test/saeeol/run-network.test.ts +224 -0
- package/test/saeeol/semantic-search.test.ts +186 -0
- package/test/saeeol/server/permission-allow-everything.test.ts +125 -0
- package/test/saeeol/session/instruction-substitution.test.ts +72 -0
- package/test/saeeol/session/platform-attribution.test.ts +118 -0
- package/test/saeeol/session/session.test.ts +105 -0
- package/test/saeeol/session-compaction-cap.test.ts +399 -0
- package/test/saeeol/session-compaction-chunks.test.ts +501 -0
- package/test/saeeol/session-compaction-safety.test.ts +481 -0
- package/test/saeeol/session-fork-remap.test.ts +251 -0
- package/test/saeeol/session-import-service.test.ts +114 -0
- package/test/saeeol/session-list.test.ts +47 -0
- package/test/saeeol/session-message-metadata.test.ts +128 -0
- package/test/saeeol/session-overflow.test.ts +78 -0
- package/test/saeeol/session-processor-empty-tool-calls.test.ts +571 -0
- package/test/saeeol/session-processor-network-offline.test.ts +204 -0
- package/test/saeeol/session-processor-retry-limit.test.ts +238 -0
- package/test/saeeol/session-processor-review-telemetry.test.ts +82 -0
- package/test/saeeol/session-prompt-compaction-safety.test.ts +517 -0
- package/test/saeeol/session-prompt-queue.test.ts +815 -0
- package/test/saeeol/sessions/inflight-cache.test.ts +157 -0
- package/test/saeeol/sessions/ingest-queue.test.ts +402 -0
- package/test/saeeol/sessions/remote-protocol.test.ts +258 -0
- package/test/saeeol/sessions/remote-sender.test.ts +1036 -0
- package/test/saeeol/sessions/remote-ws.test.ts +367 -0
- package/test/saeeol/sessions/sessions-enable-remote.test.disable +181 -0
- package/test/saeeol/slot-prop-reactivity.test.ts +142 -0
- package/test/saeeol/snapshot-cache.test.ts +84 -0
- package/test/saeeol/snapshot-freeze-repro.test.ts +100 -0
- package/test/saeeol/snapshot-track-timeout.test.ts +519 -0
- package/test/saeeol/stats-subagent-cost.test.ts +123 -0
- package/test/saeeol/suggestion/auto-dismiss.test.ts +65 -0
- package/test/saeeol/suggestion/suggestion.test.ts +145 -0
- package/test/saeeol/suggestion/tool.test.ts +298 -0
- package/test/saeeol/summary-file-diff.test.ts +28 -0
- package/test/saeeol/system-prompt.test.ts +142 -0
- package/test/saeeol/task-nesting.test.ts +193 -0
- package/test/saeeol/telemetry/feedback.test.ts +8 -0
- package/test/saeeol/todo-view.test.ts +57 -0
- package/test/saeeol/tool-encoding.test.ts +455 -0
- package/test/saeeol/tool-registry-indexing-import-failure.test.ts +49 -0
- package/test/saeeol/tool-registry-indexing.test.ts +236 -0
- package/test/saeeol/tool-registry-semantic-import-failure.test.ts +55 -0
- package/test/saeeol/tool-task-model.test.ts +352 -0
- package/test/saeeol/transform-opus-4.7.test.ts +89 -0
- package/test/saeeol/tui-diff.test.ts +91 -0
- package/test/saeeol/tui-sync.test.ts +80 -0
- package/test/saeeol/util/url.test.ts +141 -0
- package/test/saeeol/workflows-migrator.test.ts +261 -0
- package/test/saeeol/worktree-diff-summary.test.ts +64 -0
- package/test/saeeol/worktree-diff.test.ts +223 -0
- package/test/saeeol/worktree-remove-lock.test.ts +82 -0
- package/test/server/AGENTS.md +15 -0
- package/test/server/contract.test.ts +357 -0
- package/test/server/experimental-session-list.test.ts +157 -0
- package/test/server/global-session-list.test.ts +155 -0
- package/test/server/httpapi-authorization.test.ts +103 -0
- package/test/server/httpapi-bridge.test.ts +440 -0
- package/test/server/httpapi-config.test.ts +67 -0
- package/test/server/httpapi-cors.test.ts +89 -0
- package/test/server/httpapi-event.test.ts +57 -0
- package/test/server/httpapi-experimental.test.ts +219 -0
- package/test/server/httpapi-file.test.ts +79 -0
- package/test/server/httpapi-instance-context.test.ts +237 -0
- package/test/server/httpapi-instance.legacy.test.ts +140 -0
- package/test/server/httpapi-instance.test.ts +83 -0
- package/test/server/httpapi-json-parity.test.ts +263 -0
- package/test/server/httpapi-mcp-oauth.test.ts +76 -0
- package/test/server/httpapi-mcp.test.ts +189 -0
- package/test/server/httpapi-provider.test.ts +153 -0
- package/test/server/httpapi-pty-websocket.test.ts +16 -0
- package/test/server/httpapi-pty.test.ts +175 -0
- package/test/server/httpapi-raw-route-auth.test.ts +89 -0
- package/test/server/httpapi-sdk.test.ts +681 -0
- package/test/server/httpapi-session.test.ts +464 -0
- package/test/server/httpapi-sync.test.ts +130 -0
- package/test/server/httpapi-tui.test.ts +121 -0
- package/test/server/httpapi-workspace-routing.test.ts +471 -0
- package/test/server/httpapi-workspace.test.ts +427 -0
- package/test/server/lib/conformance.ts +88 -0
- package/test/server/lib/stateful.ts +112 -0
- package/test/server/project-init-git.test.ts +113 -0
- package/test/server/proxy-util.test.ts +113 -0
- package/test/server/session-actions.test.ts +49 -0
- package/test/server/session-list.test.ts +238 -0
- package/test/server/session-messages.test.ts +167 -0
- package/test/server/session-select.test.ts +100 -0
- package/test/server/trace-attributes.test.ts +76 -0
- package/test/server/workspace-proxy.test.ts +165 -0
- package/test/server/workspace-routing.test.ts +85 -0
- package/test/session/compaction.test.ts +2420 -0
- package/test/session/instruction.test.ts +247 -0
- package/test/session/llm.test.ts +1273 -0
- package/test/session/message-v2.test.ts +1291 -0
- package/test/session/messages-pagination.test.ts +1173 -0
- package/test/session/network.test.ts +249 -0
- package/test/session/processor-effect.test.ts +847 -0
- package/test/session/prompt.test.ts +2131 -0
- package/test/session/retry.test.ts +340 -0
- package/test/session/revert-compact.test.ts +639 -0
- package/test/session/schema-decoding.test.ts +311 -0
- package/test/session/session-entry-stepper.test.ts +917 -0
- package/test/session/session-schema.test.ts +76 -0
- package/test/session/snapshot-tool-race.test.ts +257 -0
- package/test/session/structured-output-integration.test.ts +265 -0
- package/test/session/structured-output.test.ts +381 -0
- package/test/session/system.test.ts +73 -0
- package/test/share/share-next.test.ts +333 -0
- package/test/shell/shell.test.ts +99 -0
- package/test/skill/discovery.test.ts +116 -0
- package/test/skill/skill.test.ts +393 -0
- package/test/snapshot/snapshot.test.ts +1531 -0
- package/test/storage/db.test.ts +23 -0
- package/test/storage/json-migration.test.ts +832 -0
- package/test/storage/storage.test.ts +293 -0
- package/test/suggestion/suggestion.test.ts +1 -0
- package/test/sync/index.test.ts +256 -0
- package/test/tool/__snapshots__/parameters.test.ts.snap +500 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +614 -0
- package/test/tool/bash.test.ts +1225 -0
- package/test/tool/diagnostics-filter.test.ts +55 -0
- package/test/tool/edit.test.ts +754 -0
- package/test/tool/external-directory.test.ts +169 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +65179 -0
- package/test/tool/glob.test.ts +107 -0
- package/test/tool/grep.test.ts +114 -0
- package/test/tool/lsp.test.ts +187 -0
- package/test/tool/parameters.test.ts +243 -0
- package/test/tool/question.test.ts +129 -0
- package/test/tool/read.test.ts +500 -0
- package/test/tool/recall.test.ts +151 -0
- package/test/tool/registry.test.ts +203 -0
- package/test/tool/skill.test.ts +135 -0
- package/test/tool/suggest.test.ts +1 -0
- package/test/tool/task.test.ts +612 -0
- package/test/tool/tool-define.test.ts +99 -0
- package/test/tool/truncation.test.ts +260 -0
- package/test/tool/webfetch.test.ts +103 -0
- package/test/tool/write.test.ts +291 -0
- package/test/util/data-url.test.ts +14 -0
- package/test/util/effect-zod.test.ts +754 -0
- package/test/util/error.test.ts +38 -0
- package/test/util/filesystem.test.ts +656 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/glob.test.ts +164 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/log.test.ts +86 -0
- package/test/util/module.test.ts +59 -0
- package/test/util/process.test.ts +128 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/which.test.ts +100 -0
- package/test/util/wildcard.test.ts +90 -0
- package/test/workspace/workspace-restore.test.ts +296 -0
- package/src/provider/models-snapshot.d.ts +0 -2
- package/src/provider/models-snapshot.js +0 -3
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { Effect, Schema, SchemaGetter } from "effect"
|
|
3
|
+
import z from "zod"
|
|
4
|
+
|
|
5
|
+
import { zod, ZodOverride } from "../../src/util/effect-zod"
|
|
6
|
+
|
|
7
|
+
function json(schema: z.ZodTypeAny) {
|
|
8
|
+
const { $schema: _, ...rest } = z.toJSONSchema(schema)
|
|
9
|
+
return rest
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("util.effect-zod", () => {
|
|
13
|
+
test("converts class schemas for route dto shapes", () => {
|
|
14
|
+
class Method extends Schema.Class<Method>("ProviderAuthMethod")({
|
|
15
|
+
type: Schema.Union([Schema.Literal("oauth"), Schema.Literal("api")]),
|
|
16
|
+
label: Schema.String,
|
|
17
|
+
}) {}
|
|
18
|
+
|
|
19
|
+
const out = zod(Method)
|
|
20
|
+
|
|
21
|
+
expect(out.meta()?.ref).toBe("ProviderAuthMethod")
|
|
22
|
+
expect(
|
|
23
|
+
out.parse({
|
|
24
|
+
type: "oauth",
|
|
25
|
+
label: "OAuth",
|
|
26
|
+
}),
|
|
27
|
+
).toEqual({
|
|
28
|
+
type: "oauth",
|
|
29
|
+
label: "OAuth",
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("converts structs with optional fields, arrays, and records", () => {
|
|
34
|
+
const out = zod(
|
|
35
|
+
Schema.Struct({
|
|
36
|
+
foo: Schema.optional(Schema.String),
|
|
37
|
+
bar: Schema.Array(Schema.Number),
|
|
38
|
+
baz: Schema.Record(Schema.String, Schema.Boolean),
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
out.parse({
|
|
44
|
+
bar: [1, 2],
|
|
45
|
+
baz: { ok: true },
|
|
46
|
+
}),
|
|
47
|
+
).toEqual({
|
|
48
|
+
bar: [1, 2],
|
|
49
|
+
baz: { ok: true },
|
|
50
|
+
})
|
|
51
|
+
expect(
|
|
52
|
+
out.parse({
|
|
53
|
+
foo: "hi",
|
|
54
|
+
bar: [1],
|
|
55
|
+
baz: { ok: false },
|
|
56
|
+
}),
|
|
57
|
+
).toEqual({
|
|
58
|
+
foo: "hi",
|
|
59
|
+
bar: [1],
|
|
60
|
+
baz: { ok: false },
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe("Tuples", () => {
|
|
65
|
+
test("fixed-length tuple parses matching array", () => {
|
|
66
|
+
const out = zod(Schema.Tuple([Schema.String, Schema.Number]))
|
|
67
|
+
expect(out.parse(["a", 1])).toEqual(["a", 1])
|
|
68
|
+
expect(out.safeParse(["a"]).success).toBe(false)
|
|
69
|
+
expect(out.safeParse(["a", "b"]).success).toBe(false)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("single-element tuple parses a one-element array", () => {
|
|
73
|
+
const out = zod(Schema.Tuple([Schema.Boolean]))
|
|
74
|
+
expect(out.parse([true])).toEqual([true])
|
|
75
|
+
expect(out.safeParse([true, false]).success).toBe(false)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test("tuple inside a union picks the right branch", () => {
|
|
79
|
+
const out = zod(Schema.Union([Schema.String, Schema.Tuple([Schema.String, Schema.Number])]))
|
|
80
|
+
expect(out.parse("hello")).toBe("hello")
|
|
81
|
+
expect(out.parse(["foo", 42])).toEqual(["foo", 42])
|
|
82
|
+
expect(out.safeParse(["foo"]).success).toBe(false)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("plain arrays still work (no element positions)", () => {
|
|
86
|
+
const out = zod(Schema.Array(Schema.String))
|
|
87
|
+
expect(out.parse(["a", "b", "c"])).toEqual(["a", "b", "c"])
|
|
88
|
+
expect(out.parse([])).toEqual([])
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("string literal unions produce z.enum with enum in JSON Schema", () => {
|
|
93
|
+
const Action = Schema.Literals(["allow", "deny", "ask"])
|
|
94
|
+
const out = zod(Action)
|
|
95
|
+
|
|
96
|
+
expect(out.parse("allow")).toBe("allow")
|
|
97
|
+
expect(out.parse("deny")).toBe("deny")
|
|
98
|
+
expect(() => out.parse("nope")).toThrow()
|
|
99
|
+
|
|
100
|
+
// Matches native z.enum JSON Schema output
|
|
101
|
+
const bridged = json(out)
|
|
102
|
+
const native = json(z.enum(["allow", "deny", "ask"]))
|
|
103
|
+
expect(bridged).toEqual(native)
|
|
104
|
+
expect(bridged.enum).toEqual(["allow", "deny", "ask"])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("ZodOverride annotation provides the Zod schema for branded IDs", () => {
|
|
108
|
+
const override = z.string().startsWith("per")
|
|
109
|
+
const ID = Schema.String.annotate({ [ZodOverride]: override }).pipe(Schema.brand("TestID"))
|
|
110
|
+
|
|
111
|
+
const Parent = Schema.Struct({ id: ID, name: Schema.String })
|
|
112
|
+
const out = zod(Parent)
|
|
113
|
+
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
115
|
+
expect((out as any).parse({ id: "per_abc", name: "test" })).toEqual({ id: "per_abc", name: "test" })
|
|
116
|
+
|
|
117
|
+
const schema = json(out) as any
|
|
118
|
+
expect(schema.properties.id).toEqual({ type: "string", pattern: "^per.*" })
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("Schema.Class nested in a parent preserves ref via identifier", () => {
|
|
122
|
+
class Inner extends Schema.Class<Inner>("MyInner")({
|
|
123
|
+
value: Schema.String,
|
|
124
|
+
}) {}
|
|
125
|
+
|
|
126
|
+
class Outer extends Schema.Class<Outer>("MyOuter")({
|
|
127
|
+
inner: Inner,
|
|
128
|
+
}) {}
|
|
129
|
+
|
|
130
|
+
const out = zod(Outer)
|
|
131
|
+
expect(out.meta()?.ref).toBe("MyOuter")
|
|
132
|
+
|
|
133
|
+
const shape = (out as any).shape ?? (out as any)._def?.shape?.()
|
|
134
|
+
expect(shape.inner.meta()?.ref).toBe("MyInner")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("Schema.Class preserves identifier and uses enum format", () => {
|
|
138
|
+
class Rule extends Schema.Class<Rule>("PermissionRule")({
|
|
139
|
+
permission: Schema.String,
|
|
140
|
+
pattern: Schema.String,
|
|
141
|
+
action: Schema.Literals(["allow", "deny", "ask"]),
|
|
142
|
+
}) {}
|
|
143
|
+
|
|
144
|
+
const out = zod(Rule)
|
|
145
|
+
expect(out.meta()?.ref).toBe("PermissionRule")
|
|
146
|
+
|
|
147
|
+
const schema = json(out) as any
|
|
148
|
+
expect(schema.properties.action).toEqual({
|
|
149
|
+
type: "string",
|
|
150
|
+
enum: ["allow", "deny", "ask"],
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test("ZodOverride on ID carries pattern through Schema.Class", () => {
|
|
155
|
+
const ID = Schema.String.annotate({
|
|
156
|
+
[ZodOverride]: z.string().startsWith("per"),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
class Request extends Schema.Class<Request>("TestRequest")({
|
|
160
|
+
id: ID,
|
|
161
|
+
name: Schema.String,
|
|
162
|
+
}) {}
|
|
163
|
+
|
|
164
|
+
const schema = json(zod(Request)) as any
|
|
165
|
+
expect(schema.properties.id).toEqual({ type: "string", pattern: "^per.*" })
|
|
166
|
+
expect(schema.properties.name).toEqual({ type: "string" })
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test("Permission schemas match original Zod equivalents", () => {
|
|
170
|
+
const MsgID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("msg") })
|
|
171
|
+
const PerID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("per") })
|
|
172
|
+
const SesID = Schema.String.annotate({ [ZodOverride]: z.string().startsWith("ses") })
|
|
173
|
+
|
|
174
|
+
class Tool extends Schema.Class<Tool>("PermissionTool")({
|
|
175
|
+
messageID: MsgID,
|
|
176
|
+
callID: Schema.String,
|
|
177
|
+
}) {}
|
|
178
|
+
|
|
179
|
+
class Request extends Schema.Class<Request>("PermissionRequest")({
|
|
180
|
+
id: PerID,
|
|
181
|
+
sessionID: SesID,
|
|
182
|
+
permission: Schema.String,
|
|
183
|
+
patterns: Schema.Array(Schema.String),
|
|
184
|
+
metadata: Schema.Record(Schema.String, Schema.Unknown),
|
|
185
|
+
always: Schema.Array(Schema.String),
|
|
186
|
+
tool: Schema.optional(Tool),
|
|
187
|
+
}) {}
|
|
188
|
+
|
|
189
|
+
const bridged = json(zod(Request)) as any
|
|
190
|
+
expect(bridged.properties.id).toEqual({ type: "string", pattern: "^per.*" })
|
|
191
|
+
expect(bridged.properties.sessionID).toEqual({ type: "string", pattern: "^ses.*" })
|
|
192
|
+
expect(bridged.properties.permission).toEqual({ type: "string" })
|
|
193
|
+
expect(bridged.required?.sort()).toEqual(["id", "sessionID", "permission", "patterns", "metadata", "always"].sort())
|
|
194
|
+
|
|
195
|
+
// Tool field is present with the ref from Schema.Class identifier
|
|
196
|
+
const toolSchema = json(zod(Tool)) as any
|
|
197
|
+
expect(toolSchema.properties.messageID).toEqual({ type: "string", pattern: "^msg.*" })
|
|
198
|
+
expect(toolSchema.properties.callID).toEqual({ type: "string" })
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test("ZodOverride survives Schema.brand", () => {
|
|
202
|
+
const override = z.string().startsWith("ses")
|
|
203
|
+
const ID = Schema.String.annotate({ [ZodOverride]: override }).pipe(Schema.brand("SessionID"))
|
|
204
|
+
|
|
205
|
+
// The branded schema's AST still has the override
|
|
206
|
+
class Parent extends Schema.Class<Parent>("Parent")({
|
|
207
|
+
sessionID: ID,
|
|
208
|
+
}) {}
|
|
209
|
+
|
|
210
|
+
const schema = json(zod(Parent)) as any
|
|
211
|
+
expect(schema.properties.sessionID).toEqual({ type: "string", pattern: "^ses.*" })
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe("Schema.check translation", () => {
|
|
215
|
+
test("filter returning string triggers refinement with that message", () => {
|
|
216
|
+
const isEven = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "expected an even number"))
|
|
217
|
+
const schema = zod(Schema.Number.check(isEven))
|
|
218
|
+
|
|
219
|
+
expect(schema.parse(4)).toBe(4)
|
|
220
|
+
const result = schema.safeParse(3)
|
|
221
|
+
expect(result.success).toBe(false)
|
|
222
|
+
expect(result.error!.issues[0].message).toBe("expected an even number")
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("filter returning false triggers refinement with fallback message", () => {
|
|
226
|
+
const nonEmpty = Schema.makeFilter((s: string) => s.length > 0)
|
|
227
|
+
const schema = zod(Schema.String.check(nonEmpty))
|
|
228
|
+
|
|
229
|
+
expect(schema.parse("hi")).toBe("hi")
|
|
230
|
+
const result = schema.safeParse("")
|
|
231
|
+
expect(result.success).toBe(false)
|
|
232
|
+
expect(result.error!.issues[0].message).toMatch(/./)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("filter returning undefined passes validation", () => {
|
|
236
|
+
const alwaysOk = Schema.makeFilter(() => undefined)
|
|
237
|
+
const schema = zod(Schema.Number.check(alwaysOk))
|
|
238
|
+
|
|
239
|
+
expect(schema.parse(42)).toBe(42)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test("annotations.message on the filter is used when filter returns false", () => {
|
|
243
|
+
const positive = Schema.makeFilter((n: number) => n > 0, { message: "must be positive" })
|
|
244
|
+
const schema = zod(Schema.Number.check(positive))
|
|
245
|
+
|
|
246
|
+
const result = schema.safeParse(-1)
|
|
247
|
+
expect(result.success).toBe(false)
|
|
248
|
+
expect(result.error!.issues[0].message).toBe("must be positive")
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test("cross-field check on a record flags missing key", () => {
|
|
252
|
+
const hasKey = Schema.makeFilter((data: Record<string, { enabled: boolean }>) =>
|
|
253
|
+
"required" in data ? undefined : "missing 'required' key",
|
|
254
|
+
)
|
|
255
|
+
const schema = zod(Schema.Record(Schema.String, Schema.Struct({ enabled: Schema.Boolean })).check(hasKey))
|
|
256
|
+
|
|
257
|
+
expect(schema.parse({ required: { enabled: true } })).toEqual({
|
|
258
|
+
required: { enabled: true },
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const result = schema.safeParse({ other: { enabled: true } })
|
|
262
|
+
expect(result.success).toBe(false)
|
|
263
|
+
expect(result.error!.issues[0].message).toBe("missing 'required' key")
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe("StructWithRest / catchall", () => {
|
|
268
|
+
test("struct with a string-keyed record rest parses known AND extra keys", () => {
|
|
269
|
+
const schema = zod(
|
|
270
|
+
Schema.StructWithRest(
|
|
271
|
+
Schema.Struct({
|
|
272
|
+
apiKey: Schema.optional(Schema.String),
|
|
273
|
+
baseURL: Schema.optional(Schema.String),
|
|
274
|
+
}),
|
|
275
|
+
[Schema.Record(Schema.String, Schema.Unknown)],
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
// Known fields come through as declared
|
|
280
|
+
expect(schema.parse({ apiKey: "sk-x" })).toEqual({ apiKey: "sk-x" })
|
|
281
|
+
|
|
282
|
+
// Extra keys are preserved (catchall)
|
|
283
|
+
expect(
|
|
284
|
+
schema.parse({
|
|
285
|
+
apiKey: "sk-x",
|
|
286
|
+
baseURL: "https://api.example.com",
|
|
287
|
+
customField: "anything",
|
|
288
|
+
nested: { foo: 1 },
|
|
289
|
+
}),
|
|
290
|
+
).toEqual({
|
|
291
|
+
apiKey: "sk-x",
|
|
292
|
+
baseURL: "https://api.example.com",
|
|
293
|
+
customField: "anything",
|
|
294
|
+
nested: { foo: 1 },
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test("catchall value type constrains the extras", () => {
|
|
299
|
+
const schema = zod(
|
|
300
|
+
Schema.StructWithRest(
|
|
301
|
+
Schema.Struct({
|
|
302
|
+
count: Schema.Number,
|
|
303
|
+
}),
|
|
304
|
+
[Schema.Record(Schema.String, Schema.Number)],
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
// Known field + numeric extras
|
|
309
|
+
expect(schema.parse({ count: 10, a: 1, b: 2 })).toEqual({ count: 10, a: 1, b: 2 })
|
|
310
|
+
|
|
311
|
+
// Non-numeric extra is rejected
|
|
312
|
+
expect(schema.safeParse({ count: 10, bad: "not a number" }).success).toBe(false)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test("JSON schema output marks additionalProperties appropriately", () => {
|
|
316
|
+
const schema = zod(
|
|
317
|
+
Schema.StructWithRest(
|
|
318
|
+
Schema.Struct({
|
|
319
|
+
id: Schema.String,
|
|
320
|
+
}),
|
|
321
|
+
[Schema.Record(Schema.String, Schema.Unknown)],
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
const shape = json(schema) as { additionalProperties?: unknown }
|
|
325
|
+
// Presence of `additionalProperties` (truthy or a schema) signals catchall.
|
|
326
|
+
expect(shape.additionalProperties).not.toBe(false)
|
|
327
|
+
expect(shape.additionalProperties).toBeDefined()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test("plain struct without rest still emits additionalProperties unchanged (regression)", () => {
|
|
331
|
+
const schema = zod(Schema.Struct({ id: Schema.String }))
|
|
332
|
+
expect(schema.parse({ id: "x" })).toEqual({ id: "x" })
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
describe("transforms (Schema.decodeTo)", () => {
|
|
337
|
+
test("Number -> pseudo-Duration (seconds) applies the decode function", () => {
|
|
338
|
+
// Models the account/account.ts DurationFromSeconds pattern.
|
|
339
|
+
const SecondsToMs = Schema.Number.pipe(
|
|
340
|
+
Schema.decodeTo(Schema.Number, {
|
|
341
|
+
decode: SchemaGetter.transform((n: number) => n * 1000),
|
|
342
|
+
encode: SchemaGetter.transform((ms: number) => ms / 1000),
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
const schema = zod(SecondsToMs)
|
|
347
|
+
expect(schema.parse(3)).toBe(3000)
|
|
348
|
+
expect(schema.parse(0)).toBe(0)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test("String -> Number via parseInt decode", () => {
|
|
352
|
+
const ParsedInt = Schema.String.pipe(
|
|
353
|
+
Schema.decodeTo(Schema.Number, {
|
|
354
|
+
decode: SchemaGetter.transform((s: string) => Number.parseInt(s, 10)),
|
|
355
|
+
encode: SchemaGetter.transform((n: number) => String(n)),
|
|
356
|
+
}),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const schema = zod(ParsedInt)
|
|
360
|
+
expect(schema.parse("42")).toBe(42)
|
|
361
|
+
expect(schema.parse("0")).toBe(0)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test("transform inside a struct field applies per-field", () => {
|
|
365
|
+
const Field = Schema.Number.pipe(
|
|
366
|
+
Schema.decodeTo(Schema.Number, {
|
|
367
|
+
decode: SchemaGetter.transform((n: number) => n + 1),
|
|
368
|
+
encode: SchemaGetter.transform((n: number) => n - 1),
|
|
369
|
+
}),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
const schema = zod(
|
|
373
|
+
Schema.Struct({
|
|
374
|
+
plain: Schema.Number,
|
|
375
|
+
bumped: Field,
|
|
376
|
+
}),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
expect(schema.parse({ plain: 5, bumped: 10 })).toEqual({ plain: 5, bumped: 11 })
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test("chained decodeTo composes transforms in order", () => {
|
|
383
|
+
// String -> Number (parseInt) -> Number (doubled).
|
|
384
|
+
// Exercises the encoded() reduce, not just a single link.
|
|
385
|
+
const Chained = Schema.String.pipe(
|
|
386
|
+
Schema.decodeTo(Schema.Number, {
|
|
387
|
+
decode: SchemaGetter.transform((s: string) => Number.parseInt(s, 10)),
|
|
388
|
+
encode: SchemaGetter.transform((n: number) => String(n)),
|
|
389
|
+
}),
|
|
390
|
+
Schema.decodeTo(Schema.Number, {
|
|
391
|
+
decode: SchemaGetter.transform((n: number) => n * 2),
|
|
392
|
+
encode: SchemaGetter.transform((n: number) => n / 2),
|
|
393
|
+
}),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
const schema = zod(Chained)
|
|
397
|
+
expect(schema.parse("21")).toBe(42)
|
|
398
|
+
expect(schema.parse("0")).toBe(0)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
test("Schema.Class is unaffected by transform walker (returns plain object, not instance)", () => {
|
|
402
|
+
// Schema.Class uses Declaration + encoding under the hood to construct
|
|
403
|
+
// class instances. The walker must NOT apply that transform, or zod
|
|
404
|
+
// parsing would return class instances instead of plain objects.
|
|
405
|
+
class Method extends Schema.Class<Method>("TxTestMethod")({
|
|
406
|
+
type: Schema.String,
|
|
407
|
+
value: Schema.Number,
|
|
408
|
+
}) {}
|
|
409
|
+
|
|
410
|
+
const schema = zod(Method)
|
|
411
|
+
const parsed = schema.parse({ type: "oauth", value: 1 })
|
|
412
|
+
expect(parsed).toEqual({ type: "oauth", value: 1 })
|
|
413
|
+
// Guardrail: ensure we didn't get back a Method instance.
|
|
414
|
+
expect(parsed).not.toBeInstanceOf(Method)
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe("optimizations", () => {
|
|
419
|
+
test("walk() memoizes by AST identity — same AST node returns same Zod", () => {
|
|
420
|
+
const shared = Schema.Struct({ id: Schema.String, name: Schema.String })
|
|
421
|
+
const left = zod(shared)
|
|
422
|
+
const right = zod(shared)
|
|
423
|
+
expect(left).toBe(right)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
test("nested reuse of the same AST reuses the cached Zod child", () => {
|
|
427
|
+
// Two different parents embed the same inner schema. The inner zod
|
|
428
|
+
// child should be identical by reference inside both parents.
|
|
429
|
+
class Inner extends Schema.Class<Inner>("MemoTestInner")({
|
|
430
|
+
value: Schema.String,
|
|
431
|
+
}) {}
|
|
432
|
+
|
|
433
|
+
class OuterA extends Schema.Class<OuterA>("MemoTestOuterA")({
|
|
434
|
+
inner: Inner,
|
|
435
|
+
}) {}
|
|
436
|
+
|
|
437
|
+
class OuterB extends Schema.Class<OuterB>("MemoTestOuterB")({
|
|
438
|
+
inner: Inner,
|
|
439
|
+
}) {}
|
|
440
|
+
|
|
441
|
+
const shapeA = (zod(OuterA) as any).shape ?? (zod(OuterA) as any)._def?.shape?.()
|
|
442
|
+
const shapeB = (zod(OuterB) as any).shape ?? (zod(OuterB) as any)._def?.shape?.()
|
|
443
|
+
expect(shapeA.inner).toBe(shapeB.inner)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
test("multiple checks run in a single refinement layer (all fire on one value)", () => {
|
|
447
|
+
// Three checks attached to the same schema. All three must run and
|
|
448
|
+
// report — asserting that no check silently got dropped when we
|
|
449
|
+
// flattened into one superRefine.
|
|
450
|
+
const positive = Schema.makeFilter((n: number) => (n > 0 ? undefined : "not positive"))
|
|
451
|
+
const even = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "not even"))
|
|
452
|
+
const under100 = Schema.makeFilter((n: number) => (n < 100 ? undefined : "too big"))
|
|
453
|
+
|
|
454
|
+
const schema = zod(Schema.Number.check(positive).check(even).check(under100))
|
|
455
|
+
|
|
456
|
+
const neg = schema.safeParse(-3)
|
|
457
|
+
expect(neg.success).toBe(false)
|
|
458
|
+
expect(neg.error!.issues.map((i) => i.message)).toEqual(expect.arrayContaining(["not positive", "not even"]))
|
|
459
|
+
|
|
460
|
+
const big = schema.safeParse(101)
|
|
461
|
+
expect(big.success).toBe(false)
|
|
462
|
+
expect(big.error!.issues.map((i) => i.message)).toContain("too big")
|
|
463
|
+
|
|
464
|
+
// Passing value satisfies all three
|
|
465
|
+
expect(schema.parse(42)).toBe(42)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
test("FilterGroup flattens into the single refinement layer alongside its siblings", () => {
|
|
469
|
+
const positive = Schema.makeFilter((n: number) => (n > 0 ? undefined : "not positive"))
|
|
470
|
+
const even = Schema.makeFilter((n: number) => (n % 2 === 0 ? undefined : "not even"))
|
|
471
|
+
const group = Schema.makeFilterGroup([positive, even])
|
|
472
|
+
const under100 = Schema.makeFilter((n: number) => (n < 100 ? undefined : "too big"))
|
|
473
|
+
|
|
474
|
+
const schema = zod(Schema.Number.check(group).check(under100))
|
|
475
|
+
|
|
476
|
+
const bad = schema.safeParse(-3)
|
|
477
|
+
expect(bad.success).toBe(false)
|
|
478
|
+
expect(bad.error!.issues.map((i) => i.message)).toEqual(expect.arrayContaining(["not positive", "not even"]))
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
describe("well-known refinement translation", () => {
|
|
483
|
+
test("Schema.isInt emits type: integer in JSON Schema", () => {
|
|
484
|
+
const schema = zod(Schema.Number.check(Schema.isInt()))
|
|
485
|
+
const native = json(z.number().int())
|
|
486
|
+
expect(json(schema)).toEqual(native)
|
|
487
|
+
expect(schema.parse(3)).toBe(3)
|
|
488
|
+
expect(schema.safeParse(1.5).success).toBe(false)
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
test("Schema.isGreaterThan(0) emits exclusiveMinimum: 0", () => {
|
|
492
|
+
const schema = zod(Schema.Number.check(Schema.isGreaterThan(0)))
|
|
493
|
+
expect((json(schema) as any).exclusiveMinimum).toBe(0)
|
|
494
|
+
expect(schema.parse(1)).toBe(1)
|
|
495
|
+
expect(schema.safeParse(0).success).toBe(false)
|
|
496
|
+
expect(schema.safeParse(-1).success).toBe(false)
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
test("Schema.isGreaterThanOrEqualTo(0) emits minimum: 0", () => {
|
|
500
|
+
const schema = zod(Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)))
|
|
501
|
+
expect((json(schema) as any).minimum).toBe(0)
|
|
502
|
+
expect(schema.parse(0)).toBe(0)
|
|
503
|
+
expect(schema.safeParse(-1).success).toBe(false)
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
test("Schema.isLessThan(10) emits exclusiveMaximum: 10", () => {
|
|
507
|
+
const schema = zod(Schema.Number.check(Schema.isLessThan(10)))
|
|
508
|
+
expect((json(schema) as any).exclusiveMaximum).toBe(10)
|
|
509
|
+
expect(schema.parse(9)).toBe(9)
|
|
510
|
+
expect(schema.safeParse(10).success).toBe(false)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
test("Schema.isLessThanOrEqualTo(10) emits maximum: 10", () => {
|
|
514
|
+
const schema = zod(Schema.Number.check(Schema.isLessThanOrEqualTo(10)))
|
|
515
|
+
expect((json(schema) as any).maximum).toBe(10)
|
|
516
|
+
expect(schema.parse(10)).toBe(10)
|
|
517
|
+
expect(schema.safeParse(11).success).toBe(false)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
test("Schema.isMultipleOf(5) emits multipleOf: 5", () => {
|
|
521
|
+
const schema = zod(Schema.Number.check(Schema.isMultipleOf(5)))
|
|
522
|
+
expect((json(schema) as any).multipleOf).toBe(5)
|
|
523
|
+
expect(schema.parse(10)).toBe(10)
|
|
524
|
+
expect(schema.safeParse(7).success).toBe(false)
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
test("Schema.isFinite validates at runtime", () => {
|
|
528
|
+
const schema = zod(Schema.Number.check(Schema.isFinite()))
|
|
529
|
+
expect(schema.parse(1)).toBe(1)
|
|
530
|
+
expect(schema.safeParse(Infinity).success).toBe(false)
|
|
531
|
+
expect(schema.safeParse(NaN).success).toBe(false)
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
test("chained isInt + isGreaterThan(0) matches z.number().int().positive()", () => {
|
|
535
|
+
const schema = zod(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)))
|
|
536
|
+
const native = json(z.number().int().positive())
|
|
537
|
+
expect(json(schema)).toEqual(native)
|
|
538
|
+
expect(schema.parse(3)).toBe(3)
|
|
539
|
+
expect(schema.safeParse(0).success).toBe(false)
|
|
540
|
+
expect(schema.safeParse(1.5).success).toBe(false)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test("chained isInt + isGreaterThanOrEqualTo(0) matches z.number().int().min(0)", () => {
|
|
544
|
+
const schema = zod(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)))
|
|
545
|
+
const native = json(z.number().int().min(0))
|
|
546
|
+
expect(json(schema)).toEqual(native)
|
|
547
|
+
expect(schema.parse(0)).toBe(0)
|
|
548
|
+
expect(schema.safeParse(-1).success).toBe(false)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
test("Schema.isBetween emits both bounds", () => {
|
|
552
|
+
const schema = zod(Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 10 })))
|
|
553
|
+
const shape = json(schema) as any
|
|
554
|
+
expect(shape.minimum).toBe(1)
|
|
555
|
+
expect(shape.maximum).toBe(10)
|
|
556
|
+
expect(schema.parse(5)).toBe(5)
|
|
557
|
+
expect(schema.safeParse(11).success).toBe(false)
|
|
558
|
+
expect(schema.safeParse(0).success).toBe(false)
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
test("Schema.isBetween with exclusive bounds emits exclusiveMinimum/Maximum", () => {
|
|
562
|
+
const schema = zod(
|
|
563
|
+
Schema.Number.check(
|
|
564
|
+
Schema.isBetween({ minimum: 1, maximum: 10, exclusiveMinimum: true, exclusiveMaximum: true }),
|
|
565
|
+
),
|
|
566
|
+
)
|
|
567
|
+
const shape = json(schema) as any
|
|
568
|
+
expect(shape.exclusiveMinimum).toBe(1)
|
|
569
|
+
expect(shape.exclusiveMaximum).toBe(10)
|
|
570
|
+
expect(schema.parse(5)).toBe(5)
|
|
571
|
+
expect(schema.safeParse(1).success).toBe(false)
|
|
572
|
+
expect(schema.safeParse(10).success).toBe(false)
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
test("Schema.isInt32 (FilterGroup) produces integer bounds", () => {
|
|
576
|
+
const schema = zod(Schema.Number.check(Schema.isInt32()))
|
|
577
|
+
const shape = json(schema) as any
|
|
578
|
+
expect(shape.type).toBe("integer")
|
|
579
|
+
expect(shape.minimum).toBe(-2147483648)
|
|
580
|
+
expect(shape.maximum).toBe(2147483647)
|
|
581
|
+
expect(schema.parse(42)).toBe(42)
|
|
582
|
+
expect(schema.safeParse(1.5).success).toBe(false)
|
|
583
|
+
expect(schema.safeParse(2147483648).success).toBe(false)
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
test("Schema.isMinLength on string emits minLength", () => {
|
|
587
|
+
const schema = zod(Schema.String.check(Schema.isMinLength(3)))
|
|
588
|
+
expect((json(schema) as any).minLength).toBe(3)
|
|
589
|
+
expect(schema.parse("abc")).toBe("abc")
|
|
590
|
+
expect(schema.safeParse("ab").success).toBe(false)
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
test("Schema.isMaxLength on string emits maxLength", () => {
|
|
594
|
+
const schema = zod(Schema.String.check(Schema.isMaxLength(5)))
|
|
595
|
+
expect((json(schema) as any).maxLength).toBe(5)
|
|
596
|
+
expect(schema.parse("abcde")).toBe("abcde")
|
|
597
|
+
expect(schema.safeParse("abcdef").success).toBe(false)
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
test("Schema.isLengthBetween on string emits both bounds", () => {
|
|
601
|
+
const schema = zod(Schema.String.check(Schema.isLengthBetween(2, 4)))
|
|
602
|
+
const shape = json(schema) as any
|
|
603
|
+
expect(shape.minLength).toBe(2)
|
|
604
|
+
expect(shape.maxLength).toBe(4)
|
|
605
|
+
expect(schema.parse("abc")).toBe("abc")
|
|
606
|
+
expect(schema.safeParse("a").success).toBe(false)
|
|
607
|
+
expect(schema.safeParse("abcde").success).toBe(false)
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
test("Schema.isMinLength on array emits minItems", () => {
|
|
611
|
+
const schema = zod(Schema.Array(Schema.String).check(Schema.isMinLength(1)))
|
|
612
|
+
expect((json(schema) as any).minItems).toBe(1)
|
|
613
|
+
expect(schema.parse(["x"])).toEqual(["x"])
|
|
614
|
+
expect(schema.safeParse([]).success).toBe(false)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
test("Schema.isPattern emits pattern", () => {
|
|
618
|
+
const schema = zod(Schema.String.check(Schema.isPattern(/^per/)))
|
|
619
|
+
expect((json(schema) as any).pattern).toBe("^per")
|
|
620
|
+
expect(schema.parse("per_abc")).toBe("per_abc")
|
|
621
|
+
expect(schema.safeParse("abc").success).toBe(false)
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
test("Schema.isStartsWith matches native zod .startsWith() JSON Schema", () => {
|
|
625
|
+
const schema = zod(Schema.String.check(Schema.isStartsWith("per")))
|
|
626
|
+
const native = json(z.string().startsWith("per"))
|
|
627
|
+
expect(json(schema)).toEqual(native)
|
|
628
|
+
expect(schema.parse("per_abc")).toBe("per_abc")
|
|
629
|
+
expect(schema.safeParse("abc").success).toBe(false)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
test("Schema.isEndsWith matches native zod .endsWith() JSON Schema", () => {
|
|
633
|
+
const schema = zod(Schema.String.check(Schema.isEndsWith(".json")))
|
|
634
|
+
const native = json(z.string().endsWith(".json"))
|
|
635
|
+
expect(json(schema)).toEqual(native)
|
|
636
|
+
expect(schema.parse("a.json")).toBe("a.json")
|
|
637
|
+
expect(schema.safeParse("a.txt").success).toBe(false)
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
test("Schema.isUUID emits format: uuid", () => {
|
|
641
|
+
const schema = zod(Schema.String.check(Schema.isUUID()))
|
|
642
|
+
expect((json(schema) as any).format).toBe("uuid")
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
test("mix of well-known and anonymous filters translates known and reroutes unknown to superRefine", () => {
|
|
646
|
+
// isInt is well-known (translates to .int()); the anonymous filter falls
|
|
647
|
+
// back to superRefine.
|
|
648
|
+
const notSeven = Schema.makeFilter((n: number) => (n !== 7 ? undefined : "no sevens allowed"))
|
|
649
|
+
const schema = zod(Schema.Number.check(Schema.isInt()).check(notSeven))
|
|
650
|
+
|
|
651
|
+
const shape = json(schema) as any
|
|
652
|
+
// Well-known translation is preserved — type is integer, not plain number
|
|
653
|
+
expect(shape.type).toBe("integer")
|
|
654
|
+
|
|
655
|
+
// Runtime: both constraints fire
|
|
656
|
+
expect(schema.parse(3)).toBe(3)
|
|
657
|
+
expect(schema.safeParse(1.5).success).toBe(false)
|
|
658
|
+
const seven = schema.safeParse(7)
|
|
659
|
+
expect(seven.success).toBe(false)
|
|
660
|
+
expect(seven.error!.issues[0].message).toBe("no sevens allowed")
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
test("inside a struct field, well-known refinements propagate through", () => {
|
|
664
|
+
// Mirrors config.ts port: z.number().int().positive().optional()
|
|
665
|
+
const Port = Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)))
|
|
666
|
+
const schema = zod(Schema.Struct({ port: Port }))
|
|
667
|
+
const shape = json(schema) as any
|
|
668
|
+
expect(shape.properties.port.type).toBe("integer")
|
|
669
|
+
expect(shape.properties.port.exclusiveMinimum).toBe(0)
|
|
670
|
+
})
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
describe("Schema.optionalWith defaults", () => {
|
|
674
|
+
test("parsing undefined returns the default value", () => {
|
|
675
|
+
const schema = zod(
|
|
676
|
+
Schema.Struct({
|
|
677
|
+
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
|
|
678
|
+
}),
|
|
679
|
+
)
|
|
680
|
+
expect(schema.parse({})).toEqual({ mode: "ctrl-x" })
|
|
681
|
+
expect(schema.parse({ mode: undefined })).toEqual({ mode: "ctrl-x" })
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
test("parsing a real value returns that value (default does not fire)", () => {
|
|
685
|
+
const schema = zod(
|
|
686
|
+
Schema.Struct({
|
|
687
|
+
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
|
|
688
|
+
}),
|
|
689
|
+
)
|
|
690
|
+
expect(schema.parse({ mode: "ctrl-y" })).toEqual({ mode: "ctrl-y" })
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
test("default on a number field", () => {
|
|
694
|
+
const schema = zod(
|
|
695
|
+
Schema.Struct({
|
|
696
|
+
count: Schema.Number.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(42))),
|
|
697
|
+
}),
|
|
698
|
+
)
|
|
699
|
+
expect(schema.parse({})).toEqual({ count: 42 })
|
|
700
|
+
expect(schema.parse({ count: 7 })).toEqual({ count: 7 })
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
test("multiple defaulted fields inside a struct", () => {
|
|
704
|
+
const schema = zod(
|
|
705
|
+
Schema.Struct({
|
|
706
|
+
leader: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
|
|
707
|
+
quit: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-c"))),
|
|
708
|
+
inner: Schema.String,
|
|
709
|
+
}),
|
|
710
|
+
)
|
|
711
|
+
expect(schema.parse({ inner: "hi" })).toEqual({
|
|
712
|
+
leader: "ctrl-x",
|
|
713
|
+
quit: "ctrl-c",
|
|
714
|
+
inner: "hi",
|
|
715
|
+
})
|
|
716
|
+
expect(schema.parse({ leader: "a", quit: "b", inner: "c" })).toEqual({
|
|
717
|
+
leader: "a",
|
|
718
|
+
quit: "b",
|
|
719
|
+
inner: "c",
|
|
720
|
+
})
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
test("JSON Schema output includes the default key", () => {
|
|
724
|
+
const schema = zod(
|
|
725
|
+
Schema.Struct({
|
|
726
|
+
mode: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("ctrl-x"))),
|
|
727
|
+
}),
|
|
728
|
+
)
|
|
729
|
+
const shape = json(schema) as any
|
|
730
|
+
expect(shape.properties.mode.default).toBe("ctrl-x")
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
test("default referencing a computed value resolves when evaluated", () => {
|
|
734
|
+
// Simulates `keybinds.ts` style of per-platform defaults: the default is
|
|
735
|
+
// produced by an Effect that computes a value at decode time.
|
|
736
|
+
const platform = "darwin"
|
|
737
|
+
const fallback = platform === "darwin" ? "cmd-k" : "ctrl-k"
|
|
738
|
+
const schema = zod(
|
|
739
|
+
Schema.Struct({
|
|
740
|
+
command_palette: Schema.String.pipe(Schema.optional, Schema.withDecodingDefault(Effect.sync(() => fallback))),
|
|
741
|
+
}),
|
|
742
|
+
)
|
|
743
|
+
expect(schema.parse({})).toEqual({ command_palette: "cmd-k" })
|
|
744
|
+
const shape = json(schema) as any
|
|
745
|
+
expect(shape.properties.command_palette.default).toBe("cmd-k")
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
test("plain Schema.optional (no default) still emits .optional() (regression)", () => {
|
|
749
|
+
const schema = zod(Schema.Struct({ foo: Schema.optional(Schema.String) }))
|
|
750
|
+
expect(schema.parse({})).toEqual({})
|
|
751
|
+
expect(schema.parse({ foo: "hi" })).toEqual({ foo: "hi" })
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
})
|