@bitkyc08/opencodex 2.59.0 → 2.61.0-preview.20260922
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/AGENTS_INSTALL.md +64 -0
- package/README.md +28 -1
- package/bin/ocx.mjs +382 -209
- package/gui/dist/assets/App-E64Rzjap.js +50 -0
- package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
- package/gui/dist/assets/index-DpdfZWMK.js +86 -0
- package/gui/dist/assets/index-_bpvxJu0.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/stepfun-color.svg +1 -0
- package/package.json +8 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +20 -1
- package/src/adapters/base.ts +23 -0
- package/src/adapters/coding-agent/protocol.ts +36 -6
- package/src/adapters/coding-agent/turn.ts +32 -4
- package/src/adapters/command-code.ts +52 -4
- package/src/adapters/cursor/catalog.ts +51 -7
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/protobuf-request.ts +6 -3
- package/src/adapters/cursor/request-builder.ts +20 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +14 -3
- package/src/adapters/declaration-carrier.ts +45 -0
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
- package/src/adapters/devin.ts +125 -36
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +34 -7
- package/src/adapters/google-errors.ts +7 -1
- package/src/adapters/google-http.ts +49 -10
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +66 -11
- package/src/adapters/image.ts +4 -1
- package/src/adapters/input-media-guard.ts +21 -9
- package/src/adapters/kiro/usage.ts +3 -2
- package/src/adapters/kiro-tool-fallback.ts +1 -1
- package/src/adapters/ollama-native.ts +6 -0
- package/src/adapters/openai-chat/developer-role.ts +61 -0
- package/src/adapters/openai-chat/messages.ts +46 -27
- package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
- package/src/adapters/openai-chat/passthrough.ts +33 -9
- package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +23 -58
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +17 -3
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/registry.ts +3 -2
- package/src/adapters/run-turn-queue.ts +178 -29
- package/src/adapters/xai-web-search.ts +16 -1
- package/src/bridge/errors.ts +8 -2
- package/src/bridge/response-json.ts +9 -1
- package/src/bridge/sse.ts +13 -151
- package/src/chat/inbound.ts +141 -5
- package/src/claude/desktop-3p.ts +7 -1
- package/src/claude/desktop-first-party.ts +183 -0
- package/src/claude/desktop-gateway-state.ts +41 -0
- package/src/claude/inbound-content-options.ts +6 -0
- package/src/claude/inbound.ts +32 -6
- package/src/claude/intercept/connect-proxy.ts +179 -0
- package/src/claude/intercept/listener.ts +122 -0
- package/src/claude/intercept/local-ca.ts +298 -0
- package/src/claude/intercept/runtime.ts +98 -0
- package/src/claude/intercept/settings.ts +189 -0
- package/src/cli/access.ts +87 -0
- package/src/cli/account-auth.ts +19 -0
- package/src/cli/account-extended.ts +4 -4
- package/src/cli/capabilities.ts +31 -0
- package/src/cli/claude-desktop.ts +206 -16
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/companion.ts +56 -0
- package/src/cli/dispatch.ts +46 -7
- package/src/cli/doctor.ts +28 -9
- package/src/cli/ensure-desired-integrations.ts +43 -5
- package/src/cli/help.ts +7 -9
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +203 -59
- package/src/cli/init.ts +8 -0
- package/src/cli/integrations.ts +7 -1
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +41 -2
- package/src/cli/resolve.ts +230 -0
- package/src/cli/root.ts +24 -1
- package/src/cli/start-ownership-publication.ts +56 -0
- package/src/cli/status-probes.ts +2 -18
- package/src/cli/status.ts +62 -0
- package/src/cli/stop-report.ts +143 -0
- package/src/cli/uninstall-plan.ts +9 -0
- package/src/client/machine-api.ts +2 -2
- package/src/client/machine-listener.ts +4 -7
- package/src/client/runtime.ts +26 -2
- package/src/clients/aside-profiles.ts +4 -0
- package/src/clients/config-export/zcode-store.ts +157 -0
- package/src/clients/config-export.ts +36 -0
- package/src/codex/account-store.ts +65 -0
- package/src/codex/app-server-processes.ts +72 -40
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/login-flow.ts +6 -1
- package/src/codex/auth-api/pool-quota-probe.ts +30 -7
- package/src/codex/autostart-health.ts +28 -0
- package/src/codex/catalog/build-entries.ts +2 -2
- package/src/codex/catalog/effort.ts +3 -3
- package/src/codex/catalog/gather-capture.ts +21 -2
- package/src/codex/catalog/model-hints.ts +29 -28
- package/src/codex/catalog/parsing.ts +7 -0
- package/src/codex/catalog/provider-models.ts +43 -17
- package/src/codex/catalog/retained-sync.ts +24 -28
- package/src/codex/catalog/routed-gather.ts +19 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +2 -2
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +31 -166
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/inject/config-toml.ts +41 -6
- package/src/codex/inject/paginated-openai-compat.ts +90 -0
- package/src/codex/inject.ts +18 -15
- package/src/codex/injected-marker.ts +18 -0
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/main-account.ts +6 -0
- package/src/codex/model-cache.ts +99 -6
- package/src/codex/model-entitlement-admission.ts +59 -0
- package/src/codex/model-entitlements.ts +116 -54
- package/src/codex/native-main-admission.ts +83 -0
- package/src/codex/observed-model-denials.ts +101 -8
- package/src/codex/prompt-text-probe.ts +9 -6
- package/src/codex/routing/health-store.ts +39 -0
- package/src/codex/routing/selection.ts +37 -1
- package/src/codex/routing.ts +12 -42
- package/src/codex/shim-templates.ts +29 -3
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +22 -4
- package/src/combos/failover.ts +3 -0
- package/src/companion/settings.ts +132 -0
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/atomic-write.ts +117 -5
- package/src/config/diagnostics.ts +22 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +52 -7
- package/src/config/process-state.ts +1 -1
- package/src/config/proxy-env.ts +8 -2
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +31 -1
- package/src/config/schema/leaf-validators.ts +59 -0
- package/src/config.ts +3 -3
- package/src/generated/compatibility-version.json +604 -280
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/config-io.ts +44 -10
- package/src/integrations/merge.ts +120 -13
- package/src/integrations/mutation-plan.ts +921 -0
- package/src/integrations/registry.ts +38 -0
- package/src/integrations/state.ts +78 -45
- package/src/integrations/target.ts +208 -0
- package/src/integrations/writer.ts +134 -110
- package/src/lab/conformance/fixture-provider.ts +5 -0
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/browser-launch-notice.ts +59 -0
- package/src/lib/bun-runtime.ts +6 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +63 -0
- package/src/lib/errors.ts +79 -0
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/open-url.ts +51 -7
- package/src/lib/package-tree-integrity.ts +2 -1
- package/src/lib/package-version.ts +8 -0
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-egress.ts +310 -0
- package/src/lib/provider-outbound.ts +109 -16
- package/src/lib/proxy-env.ts +82 -7
- package/src/lib/request-execution-budget.ts +72 -0
- package/src/lib/request-failure-attribution.ts +183 -0
- package/src/lib/request-failure-model.ts +236 -0
- package/src/lib/request-resend-gate.ts +138 -0
- package/src/lib/socks5-fetch.ts +136 -26
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +218 -27
- package/src/lib/standalone.ts +16 -0
- package/src/lib/upstream-retry.ts +167 -16
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/winsw.ts +2 -2
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +1 -0
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +27 -108
- package/src/oauth/login-cli.ts +80 -29
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/providers/api-key-resolve.ts +133 -0
- package/src/providers/api-key-selection.ts +5 -1
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/key-failover.ts +97 -19
- package/src/providers/key-store.ts +34 -110
- package/src/providers/model-rename-fields.ts +147 -0
- package/src/providers/model-rename-migration.ts +179 -38
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/vendor-probes-key.ts +38 -23
- package/src/providers/reasoning-metadata.ts +43 -18
- package/src/providers/registry/entries-core.ts +47 -20
- package/src/providers/registry/entries-extended.ts +63 -4
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +56 -10
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/providers/xai-transport.ts +12 -1
- package/src/reasoning-effort.ts +8 -0
- package/src/responses/apply-patch-envelope.ts +0 -12
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/function-call-compat.ts +38 -1
- package/src/responses/inline-document.ts +65 -0
- package/src/responses/input-media.ts +42 -8
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/muse-tool-name-alias.ts +19 -0
- package/src/responses/parser-content.ts +8 -2
- package/src/responses/parser-tools.ts +3 -0
- package/src/responses/parser.ts +3 -1
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +3 -0
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +108 -117
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/admission-model-scope.ts +219 -0
- package/src/server/audio-live.ts +9 -3
- package/src/server/audio-upstream.ts +18 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +60 -4
- package/src/server/chat-native.ts +19 -4
- package/src/server/claude-messages.ts +61 -20
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/grok-responses-snapshot-repair.ts +113 -11
- package/src/server/gui-freshness.ts +103 -0
- package/src/server/gui-static.ts +7 -9
- package/src/server/images.ts +59 -6
- package/src/server/index/claude-intercept-lifecycle.ts +49 -0
- package/src/server/index/serve-options.ts +142 -39
- package/src/server/index/spend-ledger-lifecycle.ts +92 -0
- package/src/server/index/startup-warnings.ts +24 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +34 -38
- package/src/server/lifecycle.ts +4 -4
- package/src/server/live-call-bindings.ts +6 -0
- package/src/server/live.ts +88 -3
- package/src/server/management/agent-settings-routes.ts +121 -36
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/companion-routes.ts +77 -0
- package/src/server/management/config-routes.ts +18 -2
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/logs-usage-routes.ts +19 -0
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/native-integration-routes.ts +103 -6
- package/src/server/management/oauth-account-routes.ts +45 -7
- package/src/server/management/route-registry.ts +20 -0
- package/src/server/management/shared.ts +28 -4
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management/usage-timeline-routes.ts +44 -0
- package/src/server/management-api.ts +10 -9
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +75 -0
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +138 -12
- package/src/server/request-log-failure-attribution.ts +99 -0
- package/src/server/request-log.ts +169 -2
- package/src/server/request-metrics.ts +298 -0
- package/src/server/responses/adapter-continuation.ts +3 -3
- package/src/server/responses/adapter-dispatch.ts +11 -6
- package/src/server/responses/codex-ws-wire.ts +34 -8
- package/src/server/responses/combo-stream-preflight.ts +168 -6
- package/src/server/responses/compact.ts +43 -10
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +8 -3
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +6 -12
- package/src/server/responses/core-opaque-recovery.ts +91 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +124 -8
- package/src/server/responses/input-admission.ts +10 -0
- package/src/server/responses/passthrough-delivery.ts +45 -15
- package/src/server/responses/passthrough-dispatch.ts +215 -44
- package/src/server/responses/passthrough-error.ts +27 -8
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +109 -18
- package/src/server/responses/request-send-budget.ts +17 -1
- package/src/server/responses/request-sidecar-auth.ts +1 -1
- package/src/server/responses/request-transport.ts +26 -6
- package/src/server/responses/reset-replay.ts +108 -0
- package/src/server/responses/run-turn-execution.ts +25 -3
- package/src/server/responses/sidecar-execution.ts +17 -2
- package/src/server/responses/ws-upstream.ts +14 -27
- package/src/server/responses-custom-tool-repair.ts +27 -54
- package/src/server/responses-request-tool-scope.ts +214 -0
- package/src/server/responses-undeclared-tool-guard.ts +35 -2
- package/src/server/search.ts +25 -1
- package/src/server/sse-payload-rewrite.ts +1 -1
- package/src/server/usage-ledger-retention.ts +73 -0
- package/src/service/cli.ts +48 -2
- package/src/service/health.ts +3 -2
- package/src/service/install-state-contract.d.mts +27 -0
- package/src/service/install-state-contract.mjs +34 -0
- package/src/service/launchd.ts +1 -1
- package/src/service/orchestration.ts +2 -4
- package/src/service/ownership-compatibility.ts +164 -0
- package/src/service/ownership-mutation-lease.d.mts +32 -0
- package/src/service/ownership-mutation-lease.mjs +211 -0
- package/src/service/repair.ts +45 -1
- package/src/service/state-lock.ts +269 -0
- package/src/service/state-record.d.mts +36 -0
- package/src/service/state-record.mjs +138 -0
- package/src/service/state.ts +582 -68
- package/src/service/windows-taskxml.ts +11 -10
- package/src/service.ts +7 -3
- package/src/tray/windows-tray.ps1 +156 -4
- package/src/types/config.ts +48 -3
- package/src/types/provider.ts +91 -0
- package/src/types/request.ts +30 -2
- package/src/types/tools.ts +33 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +207 -63
- package/src/update/job.ts +9 -5
- package/src/update/ownership-transaction.ts +47 -0
- package/src/update/restart-ownership.ts +54 -0
- package/src/update/runtime-ownership.d.mts +40 -0
- package/src/update/runtime-ownership.mjs +122 -0
- package/src/usage/attempt-delivery.ts +198 -0
- package/src/usage/cache-diagnostic.ts +305 -0
- package/src/usage/failure-fingerprint.ts +118 -0
- package/src/usage/failure-projection-cache.ts +174 -0
- package/src/usage/failure-projection.ts +174 -0
- package/src/usage/ledger-retention.ts +165 -0
- package/src/usage/log.ts +126 -79
- package/src/usage/request-outcome.ts +150 -0
- package/src/usage/retention-contract.ts +28 -0
- package/src/usage/summary.ts +2 -2
- package/src/usage/telemetry-contract.ts +237 -0
- package/src/usage/timeline.ts +236 -0
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/alpha-search.ts +21 -1
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/gui/dist/assets/index-C5IebErG.js +0 -136
- package/gui/dist/assets/index-OESInAjC.css +0 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Design of record: devlog/_fin/260802_client_toggle_api/031_wp3_writer_impl.md.
|
|
11
11
|
*/
|
|
12
|
-
import type { ManagedContribution } from "../clients/config-export";
|
|
12
|
+
import type { ManagedContribution } from "../clients/config-export/contracts";
|
|
13
13
|
|
|
14
14
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
15
15
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -22,20 +22,87 @@ function clone<T>(value: T): T {
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* `[field=value]` addresses the ONE element of a sequence whose `field` equals
|
|
25
|
-
* `value
|
|
25
|
+
* `value`, and `[v2:field=value,field=value]` the one whose every named field
|
|
26
|
+
* matches. Raycast keeps its providers as a YAML list, so the element is the
|
|
26
27
|
* smallest thing we can own there; an index would move under us the moment
|
|
27
28
|
* the user reordered their own entries. Any other segment is a plain key.
|
|
29
|
+
*
|
|
30
|
+
* A conjunction exists because one field is not always the identity. ZCode
|
|
31
|
+
* keys a model rule in its provider store by the PAIR `(providerId, modelId)`,
|
|
32
|
+
* so a `[modelId=…]` selector alone would match another provider's rule for the
|
|
33
|
+
* same model and then replace it, in a file holding every provider the user
|
|
34
|
+
* has. Addressing an element by less than what identifies it is the same
|
|
35
|
+
* defect as addressing it by index.
|
|
36
|
+
*
|
|
37
|
+
* The single-criterion spelling keeps its original grammar, where the value may
|
|
38
|
+
* itself contain commas and equals signs. A conjunction uses an explicit v2
|
|
39
|
+
* marker, so no path already written into an ownership record on disk changes
|
|
40
|
+
* meaning. Once the marker is present the whole segment must be valid; falling
|
|
41
|
+
* back to a key or v1 selector would recreate the silent reselection hazard.
|
|
28
42
|
*/
|
|
29
43
|
const ARRAY_SELECTOR = /^\[([A-Za-z_][A-Za-z0-9_]*)=([^\]]+)\]$/u;
|
|
44
|
+
const SELECTOR_FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
45
|
+
const SELECTOR_CONJUNCTION_PREFIX = "[v2:";
|
|
46
|
+
|
|
47
|
+
/** One `field=value` equality a selector requires of the element it names. */
|
|
48
|
+
export interface SelectorCriterion {
|
|
49
|
+
field: string;
|
|
50
|
+
value: string;
|
|
51
|
+
}
|
|
30
52
|
|
|
31
53
|
export type PathSegment =
|
|
32
54
|
| { kind: "key"; key: string }
|
|
33
|
-
| { kind: "select";
|
|
55
|
+
| { kind: "select"; criteria: readonly SelectorCriterion[] };
|
|
56
|
+
|
|
57
|
+
/** A reserved v2 selector marker was present, but its complete grammar was not. */
|
|
58
|
+
export class InvalidSelectorError extends Error {
|
|
59
|
+
constructor() {
|
|
60
|
+
super("invalid versioned selector");
|
|
61
|
+
this.name = "InvalidSelectorError";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validConjunctionCriteria(criteria: readonly SelectorCriterion[]): boolean {
|
|
66
|
+
return criteria.length >= 2 && criteria.every(criterion => (
|
|
67
|
+
SELECTOR_FIELD.test(criterion.field)
|
|
68
|
+
&& criterion.value.length > 0
|
|
69
|
+
&& !criterion.value.includes(",")
|
|
70
|
+
&& !criterion.value.includes("]")
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Spell a multi-field selector without colliding with persisted v1 paths. */
|
|
75
|
+
export function formatSelectorConjunction(criteria: readonly SelectorCriterion[]): string | null {
|
|
76
|
+
if (!validConjunctionCriteria(criteria)) return null;
|
|
77
|
+
return `${SELECTOR_CONJUNCTION_PREFIX}${criteria
|
|
78
|
+
.map(criterion => `${criterion.field}=${criterion.value}`)
|
|
79
|
+
.join(",")}]`;
|
|
80
|
+
}
|
|
34
81
|
|
|
35
82
|
export function parseSegment(raw: string): PathSegment {
|
|
83
|
+
if (raw.startsWith(SELECTOR_CONJUNCTION_PREFIX)) {
|
|
84
|
+
if (!raw.endsWith("]")) throw new InvalidSelectorError();
|
|
85
|
+
const parts = raw.slice(SELECTOR_CONJUNCTION_PREFIX.length, -1).split(",");
|
|
86
|
+
const criteria = parts.map(part => {
|
|
87
|
+
const equals = part.indexOf("=");
|
|
88
|
+
return equals < 0
|
|
89
|
+
? { field: "", value: "" }
|
|
90
|
+
: { field: part.slice(0, equals), value: part.slice(equals + 1) };
|
|
91
|
+
});
|
|
92
|
+
if (!validConjunctionCriteria(criteria)) throw new InvalidSelectorError();
|
|
93
|
+
return {
|
|
94
|
+
kind: "select",
|
|
95
|
+
criteria,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
36
98
|
const match = ARRAY_SELECTOR.exec(raw);
|
|
37
99
|
if (!match) return { kind: "key", key: raw };
|
|
38
|
-
return { kind: "select", field: match[1]!, value: match[2]! };
|
|
100
|
+
return { kind: "select", criteria: [{ field: match[1]!, value: match[2]! }] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The `field=value` list a selector segment spells, for messages and seeding. */
|
|
104
|
+
export function selectorPairs(criteria: readonly SelectorCriterion[]): string {
|
|
105
|
+
return criteria.map(criterion => `${criterion.field}=${criterion.value}`).join(", ");
|
|
39
106
|
}
|
|
40
107
|
|
|
41
108
|
/**
|
|
@@ -44,19 +111,21 @@ export function parseSegment(raw: string): PathSegment {
|
|
|
44
111
|
* this to an `unsafe` refusal instead.
|
|
45
112
|
*/
|
|
46
113
|
export class AmbiguousSelectorError extends Error {
|
|
47
|
-
constructor(
|
|
48
|
-
super(`more than one entry has ${
|
|
114
|
+
constructor(criteria: readonly SelectorCriterion[]) {
|
|
115
|
+
super(`more than one entry has ${selectorPairs(criteria)}`);
|
|
49
116
|
this.name = "AmbiguousSelectorError";
|
|
50
117
|
}
|
|
51
118
|
}
|
|
52
119
|
|
|
53
120
|
/** The index of the element a selector names, -1 when none matches. */
|
|
54
|
-
export function selectIndex(items: readonly unknown[],
|
|
121
|
+
export function selectIndex(items: readonly unknown[], criteria: readonly SelectorCriterion[]): number {
|
|
55
122
|
const matches: number[] = [];
|
|
56
123
|
items.forEach((item, index) => {
|
|
57
|
-
if (isPlainRecord(item) && item[field] === value)
|
|
124
|
+
if (isPlainRecord(item) && criteria.every(criterion => item[criterion.field] === criterion.value)) {
|
|
125
|
+
matches.push(index);
|
|
126
|
+
}
|
|
58
127
|
});
|
|
59
|
-
if (matches.length > 1) throw new AmbiguousSelectorError(
|
|
128
|
+
if (matches.length > 1) throw new AmbiguousSelectorError(criteria);
|
|
60
129
|
return matches[0] ?? -1;
|
|
61
130
|
}
|
|
62
131
|
|
|
@@ -64,6 +133,44 @@ function assertNever(segment: never): never {
|
|
|
64
133
|
throw new Error(`unknown path segment ${JSON.stringify(segment)}`);
|
|
65
134
|
}
|
|
66
135
|
|
|
136
|
+
/** The element a selector names, or `undefined` when none matches. */
|
|
137
|
+
function selectElement(items: readonly unknown[], segment: PathSegment & { kind: "select" }): unknown {
|
|
138
|
+
return items[selectIndex(items, segment.criteria)];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read `path` through the same segment grammar `setPath` writes through.
|
|
143
|
+
*
|
|
144
|
+
* It belongs here rather than beside the classifier because the grammar does: a
|
|
145
|
+
* reader that resolved a selector differently from the writer would report one
|
|
146
|
+
* element as ours and then rewrite another. `state` re-exports it for the
|
|
147
|
+
* callers that have always imported it from there.
|
|
148
|
+
*/
|
|
149
|
+
export function readPath(doc: unknown, path: readonly string[]): unknown {
|
|
150
|
+
let cursor: unknown = doc;
|
|
151
|
+
// Validate the complete persisted grammar before document shape can short-circuit
|
|
152
|
+
// the walk. Otherwise an absent early key can hide a malformed later selector,
|
|
153
|
+
// making an unreadable ownership record look absent and move the operation to a
|
|
154
|
+
// different file.
|
|
155
|
+
const segments = path.map(parseSegment);
|
|
156
|
+
for (const segment of segments) {
|
|
157
|
+
switch (segment.kind) {
|
|
158
|
+
case "key":
|
|
159
|
+
if (!isPlainRecord(cursor)) return undefined;
|
|
160
|
+
cursor = cursor[segment.key];
|
|
161
|
+
break;
|
|
162
|
+
case "select":
|
|
163
|
+
if (!Array.isArray(cursor)) return undefined;
|
|
164
|
+
cursor = selectElement(cursor, segment);
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
return assertNever(segment);
|
|
168
|
+
}
|
|
169
|
+
if (cursor === undefined) return undefined;
|
|
170
|
+
}
|
|
171
|
+
return cursor;
|
|
172
|
+
}
|
|
173
|
+
|
|
67
174
|
/**
|
|
68
175
|
* Write `value` at `path`, creating intermediate containers. Returns a new document.
|
|
69
176
|
*
|
|
@@ -99,7 +206,7 @@ export function setPath(doc: unknown, path: readonly string[], value: unknown):
|
|
|
99
206
|
case "select": {
|
|
100
207
|
if (!Array.isArray(read())) write([]);
|
|
101
208
|
const items = read() as unknown[];
|
|
102
|
-
const found = selectIndex(items, segment.
|
|
209
|
+
const found = selectIndex(items, segment.criteria);
|
|
103
210
|
parent = items;
|
|
104
211
|
if (found >= 0) {
|
|
105
212
|
slot = found;
|
|
@@ -107,7 +214,7 @@ export function setPath(doc: unknown, path: readonly string[], value: unknown):
|
|
|
107
214
|
// Seed the element so the selector stays true for whatever a deeper
|
|
108
215
|
// segment writes into it; a last-position select replaces it whole.
|
|
109
216
|
slot = items.length;
|
|
110
|
-
items.push(
|
|
217
|
+
items.push(Object.fromEntries(segment.criteria.map(criterion => [criterion.field, criterion.value])));
|
|
111
218
|
}
|
|
112
219
|
break;
|
|
113
220
|
}
|
|
@@ -155,7 +262,7 @@ export function deletePath(
|
|
|
155
262
|
}
|
|
156
263
|
case "select": {
|
|
157
264
|
if (!Array.isArray(container)) return { doc: root, removed: false };
|
|
158
|
-
const found = selectIndex(container, segment.
|
|
265
|
+
const found = selectIndex(container, segment.criteria);
|
|
159
266
|
if (found < 0) return { doc: root, removed: false };
|
|
160
267
|
slots.push(found);
|
|
161
268
|
chain.push(container[found] as Record<string, unknown> | unknown[]);
|
|
@@ -249,7 +356,7 @@ export function createdContainerPaths(
|
|
|
249
356
|
case "select": {
|
|
250
357
|
// A selector that matches nothing means setPath will push the element.
|
|
251
358
|
next = Array.isArray(cursor)
|
|
252
|
-
? cursor[selectIndex(cursor, segment.
|
|
359
|
+
? cursor[selectIndex(cursor, segment.criteria)]
|
|
253
360
|
: undefined;
|
|
254
361
|
break;
|
|
255
362
|
}
|