Compare commits

..
535 Commits
Author SHA1 Message Date
Will Schurman 8f2a7727cf v0.72.0 2026-10-29 16:11:52 -07:00
Will Schurman 🦦 24109bec32 feat: add notAuthorizedEntity to getUserFacingDenialReason (#732)
# Why

We've encountered a need for showing a field from the not-authorized entity in the user facing error message. While we explicitly didn't pass this in the past since it's not authorized, upon second thought and the concrete use case it should be okay since this is only able to be used within the error message. While it is technically possible to leak unauthorized data this way, the variable name and docblock should at least hint that it's not authorized and caution should be used.

# How

Add entity and evaluation context params to the message fn.

# Test Plan

Run updated tests.
2026-10-29 16:10:16 -07:00
renovate[bot] ee24879f8c chore(deps): update node.js to v24.22.1 (#731)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [node](https://nodejs.org) ([source](https://redirect.github.com/nodejs/node)) | tools | patch | `24.22.0` → `24.22.1` |

---

### Release Notes

<details>
<summary>nodejs/node (node)</summary>

### [`v24.22.1`](https://redirect.github.com/nodejs/node/releases/tag/v24.22.1): 2026-10-21, Version 24.22.1 'Krypton' (LTS), @&#8203;aduh95

[Compare Source](https://redirect.github.com/nodejs/node/compare/v24.22.0...v24.22.1)

This is a security release.

##### Notable Changes

- (CVE-2026-105481) http: ensure agent data guard works without I/O (James M Snell)
- (CVE-2026-105482) dns: validate resolve inputs for null bytes (James M Snell)
- (CVE-2026-105483) tls: fix ip normalization regression (James M Snell)
- (CVE-2026-106069) tls: reject non-canonical IP addresses (Matteo Collina)
- (CVE-2026-106073) permission: check permission model in realpathSync (RafaelGSS)
- (CVE-2026-106074) crypto: reset random caches across snapshots (Filip Skokan)
- (CVE-2026-111497) src: expose `BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH` (Antoine du Hamel)
- (CVE-2026-111497) deps: brotli: cherry-pick [`0ed4268`](https://redirect.github.com/nodejs/node/commit/0ed4268a4401abcb44720963) (Evgenii Kliuchnikov)
- crypto: update root certificates to NSS 3.131.1 (Antoine du Hamel)
- deps: update c-ares to 1.34.9 (Node.js GitHub Bot)
- deps: update timezone to 2026e (Node.js GitHub Bot)
- deps: upgrade openssl sources to openssl-3.5.9 (Node.js GitHub Bot)
- deps: update undici to 7.30.1 (Node.js GitHub Bot)

##### Commits

- \[[`2a73ee5a89`](https://redirect.github.com/nodejs/node/commit/2a73ee5a89)] - **crypto**: update root certificates to NSS 3.131.1 (Antoine du Hamel) [#&#8203;66877](https://redirect.github.com/nodejs/node/pull/66877)
- \[[`4d6990b33c`](https://redirect.github.com/nodejs/node/commit/4d6990b33c)] - **crypto**: reset random caches across snapshots (Filip Skokan) [nodejs-private/node-private#987](https://redirect.github.com/nodejs-private/node-private/pull/987)
- \[[`4b3c9bbf9f`](https://redirect.github.com/nodejs/node/commit/4b3c9bbf9f)] - **crypto**: harden X.509 conversion and formatting (Filip Skokan) [nodejs-private/node-private#1007](https://redirect.github.com/nodejs-private/node-private/pull/1007)
- \[[`9243b105b6`](https://redirect.github.com/nodejs/node/commit/9243b105b6)] - **crypto**: update root certificates to NSS 3.129 (Node.js GitHub Bot) [#&#8203;66496](https://redirect.github.com/nodejs/node/pull/66496)
- \[[`1e39cd53c7`](https://redirect.github.com/nodejs/node/commit/1e39cd53c7)] - **deps**: update c-ares to 1.34.9 (Node.js GitHub Bot) [#&#8203;66868](https://redirect.github.com/nodejs/node/pull/66868)
- \[[`49dff6f57a`](https://redirect.github.com/nodejs/node/commit/49dff6f57a)] - **deps**: brotli: cherry-pick [`0ed4268`](https://redirect.github.com/nodejs/node/commit/0ed4268a4401abcb44720963) (Evgenii Kliuchnikov) [nodejs-private/node-private#1002](https://redirect.github.com/nodejs-private/node-private/pull/1002)
- \[[`fca511adc5`](https://redirect.github.com/nodejs/node/commit/fca511adc5)] - **deps**: V8: backport [`93cd21e`](https://redirect.github.com/nodejs/node/commit/93cd21e8254b) (Joyee Cheung) [nodejs-private/node-private#1008](https://redirect.github.com/nodejs-private/node-private/pull/1008)
- \[[`18711c7567`](https://redirect.github.com/nodejs/node/commit/18711c7567)] - **deps**: update timezone to 2026e (Node.js GitHub Bot) [#&#8203;66419](https://redirect.github.com/nodejs/node/pull/66419)
- \[[`fa77cfb1ad`](https://redirect.github.com/nodejs/node/commit/fa77cfb1ad)] - **deps**: update timezone to 2026d (Node.js GitHub Bot) [#&#8203;66333](https://redirect.github.com/nodejs/node/pull/66333)
- \[[`b84f9f186b`](https://redirect.github.com/nodejs/node/commit/b84f9f186b)] - **deps**: update archs files for openssl-3.5.9 (Node.js GitHub Bot) [#&#8203;66396](https://redirect.github.com/nodejs/node/pull/66396)
- \[[`ff0664c3ba`](https://redirect.github.com/nodejs/node/commit/ff0664c3ba)] - **deps**: upgrade openssl sources to openssl-3.5.9 (Node.js GitHub Bot) [#&#8203;66396](https://redirect.github.com/nodejs/node/pull/66396)
- \[[`1d4c89ef87`](https://redirect.github.com/nodejs/node/commit/1d4c89ef87)] - **deps**: update undici to 7.30.1 (Node.js GitHub Bot) [#&#8203;66764](https://redirect.github.com/nodejs/node/pull/66764)
- \[[`843a1627dc`](https://redirect.github.com/nodejs/node/commit/843a1627dc)] - **dns**: validate resolve inputs for null bytes (James M Snell) [nodejs-private/node-private#984](https://redirect.github.com/nodejs-private/node-private/pull/984)
- \[[`96c3ccb1fc`](https://redirect.github.com/nodejs/node/commit/96c3ccb1fc)] - **http**: ensure agent data guard works without I/O (James M Snell) [nodejs-private/node-private#983](https://redirect.github.com/nodejs-private/node-private/pull/983)
- \[[`8dc18a6218`](https://redirect.github.com/nodejs/node/commit/8dc18a6218)] - **permission**: check permission model in realpathSync (RafaelGSS) [nodejs-private/node-private#973](https://redirect.github.com/nodejs-private/node-private/pull/973)
- \[[`b5f41529df`](https://redirect.github.com/nodejs/node/commit/b5f41529df)] - **src**: expose `BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH` (Antoine du Hamel) [nodejs-private/node-private#1002](https://redirect.github.com/nodejs-private/node-private/pull/1002)
- \[[`01acf0ee74`](https://redirect.github.com/nodejs/node/commit/01acf0ee74)] - **tls**: reject chains containing blocked public keys (Ulises Gascón) [#&#8203;66871](https://redirect.github.com/nodejs/node/pull/66871)
- \[[`935918e342`](https://redirect.github.com/nodejs/node/commit/935918e342)] - **tls**: reject non-canonical IP addresses (Matteo Collina) [nodejs-private/node-private#982](https://redirect.github.com/nodejs-private/node-private/pull/982)
- \[[`de49c913f2`](https://redirect.github.com/nodejs/node/commit/de49c913f2)] - **tls**: fix ip normalization regression (James M Snell) [nodejs-private/node-private#985](https://redirect.github.com/nodejs-private/node-private/pull/985)
- \[[`9dc4bb57f0`](https://redirect.github.com/nodejs/node/commit/9dc4bb57f0)] - **tools**: retry Gatekeeper check and stapling after notarization (Ryan Aslett) [#&#8203;66855](https://redirect.github.com/nodejs/node/pull/66855)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-26 08:59:22 -07:00
renovate[bot] c89ff74750 chore(deps): update postgres:18 docker digest to edb0c08 (#728)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [postgres](https://hub.docker.com/_/postgres) ([source](https://redirect.github.com/docker-library/postgres)) | digest | `74935e7` → `edb0c08` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-26 08:58:56 -07:00
renovate[bot] 0bdc8acafe chore(deps): update redis docker digest to 97ec881 (#729)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `2c2dff7` → `97ec881` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-26 08:58:51 -07:00
renovate[bot] a6088b0b95 chore(deps): update dependency jdx/mise to v2026.10.19 (#730)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jdx/mise](https://redirect.github.com/jdx/mise) | patch | `v2026.10.15` → `v2026.10.19` |

---

### Release Notes

<details>
<summary>jdx/mise</summary>

### [`v2026.10.19`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.19): : Security hardening across trust, installs and provenance; lockfiles now rebuilt completely by default

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.18...vfox-v2026.10.19)

Most of this release closes ways an untrusted repository, download host or mirror could run code, leak credentials or get around verification. Some of these fixes make mise stricter, so check **Breaking Changes** before you upgrade. Also in this release: `lockfile_mode = "generate"` is now the default, `mise lsp` sends semantic highlighting, and there is a new `{{ mise_config_dir }}` template variable.

#### Highlights

- **Trust is narrower and tied to the files you trusted.** Help output, shims, hooks, config writes and shell completion no longer trust a project or run its code without your consent. Trust also follows worktrees, symlinks and config directories correctly, and safe mode closes several more gaps.
- **Verification fails closed.** Pinned checksums, Node signatures, SLSA provenance (including the tag it was built from) and GitHub attestations are now enforced. Before, a 404 or a missing file could make mise skip them.
- **Hostile inputs are contained.** Archives, asset names, versions, lockfiles and HTTP responses can no longer write outside their directories, use unbounded memory or disk, or hang the prompt. Credentials no longer show up in install paths, lockfiles, logs or redirects.

#### Added

- **`{{ mise_config_dir }}` template variable.** It resolves to the config directory mise itself uses: `MISE_CONFIG_DIR`, or else `xdg_config_home` + `/mise`. It is available wherever `xdg_config_home` is: config templates, `.miserc.toml` and settings paths. This is useful for dotfiles variants that link into `conf.d`. [#&#8203;14886](https://redirect.github.com/jdx/mise/pull/14886) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;ponko2](https://redirect.github.com/ponko2))

  ```toml
  [dotfiles."git-tools"]
  source = "git"
  mode = "symlink"
  variants = [{ default = true, target = "{{ mise_config_dir }}/conf.d/git-tools" }]
  ```

- **Semantic highlighting in `mise lsp`.** Editors can now color task names, tools, env vars, `[vars]` keys, `_.` directives, `{{ }}`/`{% %}`/`{# #}` template delimiters and deprecated keys in `mise.toml`. To turn it off, pass `{"semanticTokens": false}` in `initializationOptions`. [#&#8203;14918](https://redirect.github.com/jdx/mise/pull/14918) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`extract_max_ratio` setting (global only).** It caps how far an archive may expand during extraction. See Security below. [#&#8203;14915](https://redirect.github.com/jdx/mise/pull/14915) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`github.enterprise_hosts` setting (global only, `MISE_GITHUB_ENTERPRISE_HOSTS`).** It lists the GitHub Enterprise hosts that receive the GitHub token on `/api/v3` URLs over https. [#&#8203;14913](https://redirect.github.com/jdx/mise/pull/14913) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **New vfox attestation fields `slsa_source_repository` and `slsa_source_tag`.** Plugins can use them to pin the repository and tag that SLSA provenance must come from. [#&#8203;14904](https://redirect.github.com/jdx/mise/pull/14904), [#&#8203;14924](https://redirect.github.com/jdx/mise/pull/14924) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Changed

- **`lockfile_mode = "generate"` is now the default.** Installs now rebuild `mise.lock` from the current requests, reuse unchanged entries and also lock the other platforms. A lockfile committed from one machine therefore no longer fails `mise install --locked` on another platform because of missing entries. Neither mode creates a lockfile (use `mise lock` or `lockfile = true`), and the lockfile format is unchanged. To keep the previous behavior, set `lockfile_mode = "merge"` or `MISE_LOCKFILE_MODE=merge`. [#&#8203;14893](https://redirect.github.com/jdx/mise/pull/14893) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Security

**Trust and safe mode**

- Several actions no longer count as consent to trust a project: printing `mise run --help`/`mise watch --help`, running a shim, a hook starting `mise run`, an MCP call, or `mise set`/`unset`/`tasks add` saving config. Config writes now ask before they trust an untrusted sibling config. A task counts as trusted only if every config that adds to it is trusted. The MCP server refuses untrusted file tasks, task names that look like file paths, and inline tool options. [#&#8203;14903](https://redirect.github.com/jdx/mise/pull/14903) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Trust now stays with the files you trusted. A forged git worktree, a symlink to your global config, a clone named `.mise`, or a project `.miserc.toml` can no longer borrow trust or hide global config. In paranoid mode, idiomatic version files, each local task file and project-named aqua registries need trust of their own. Remote git includes pinned to a commit SHA must check out exactly that commit. [#&#8203;14902](https://redirect.github.com/jdx/mise/pull/14902) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Safe mode (`MISE_SAFE=1`) now also blocks:

  - a project's lazy tools, `[deps]`, `path:` versions and spm `install_command`;
  - all S3 requests;
  - forge tokens and netrc credentials sent to hosts that project config names;
  - `env` and filesystem helpers in project templates.

  In every mode, a project `.miserc.toml` gets a restricted, size-limited template language, and `bin_path` must stay inside the tool's install directory. [#&#8203;14900](https://redirect.github.com/jdx/mise/pull/14900) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Pressing TAB no longer runs untrusted task code. The dynamic completers (`complete run=`, `complete delegate=`, `choices run=`, `mount`) only run for trusted config. mise's own completers quote typed words, and control characters are removed from candidates. Quoting for fish and PowerShell is also fixed. [#&#8203;14898](https://redirect.github.com/jdx/mise/pull/14898) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Hook commands are now rendered once, when they load. Before, a directory name containing `{{ exec(...) }}` could run code from a `cd` hook. A project's `enter`, `leave` and `watch_files` hooks now start in the project root. `mise use`, `mise upgrade --bump` and every other writer refuse to write a version containing template syntax, and a rewritten option keeps its `{{ env.X }}` template instead of the rendered secret. [#&#8203;14897](https://redirect.github.com/jdx/mise/pull/14897) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `cd`-ing into a hostile repository can no longer hang or crash `hook-env` or make mise print files from outside the repo. Causes this covers include configs linked to devices, symlink mazes in task directories, non-UTF-8 paths, oversized files, glob characters in paths, and version files that link to another file. [#&#8203;14916](https://redirect.github.com/jdx/mise/pull/14916) ([@&#8203;jdx](https://redirect.github.com/jdx))

**Install verification**

- A checksum pinned in `mise.lock` is now enforced for erlang, npm, spm and http instead of being replaced or skipped. If Node's `SHASUMS256.txt.sig` is missing, the install fails. bun and deno check the publisher's sha256 when there is no lock entry. A locked node or spm artifact that returns 404 no longer silently falls back to a source build. [#&#8203;14899](https://redirect.github.com/jdx/mise/pull/14899) ([@&#8203;jdx](https://redirect.github.com/jdx))
- SLSA, attestation and signed-checksum checks now fail closed:

  - the provenance file can no longer stand in for the artifact;
  - `slsa_signer_*` pins require provenance;
  - `mise lock` runs the cosign, minisign and SLSA checks that aqua recipes declare;
  - SHA-512 signed checksum files are compared with the artifact;
  - aqua honors `attestations_since`.

  [#&#8203;14908](https://redirect.github.com/jdx/mise/pull/14908) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The SLSA signing run must come from the workflow's repository or the tool's own repository. Attestations must name the requested repository. Public-good bundles without a Rekor entry are rejected. [#&#8203;14904](https://redirect.github.com/jdx/mise/pull/14904) ([@&#8203;jdx](https://redirect.github.com/jdx))
- SLSA provenance built from a different tag is now rejected, so a mirror can't serve an older release's genuine artifact and provenance as a newer version. [#&#8203;14924](https://redirect.github.com/jdx/mise/pull/14924) ([@&#8203;jdx](https://redirect.github.com/jdx))
- packslip signer pins are checked by repository ID, including `workflow` pins. Backdated GitLab `released_at` dates and bundles with no transparency-log time can no longer get around `minimum_release_age`. [#&#8203;14912](https://redirect.github.com/jdx/mise/pull/14912) ([@&#8203;jdx](https://redirect.github.com/jdx))

**Paths, subprocesses and resource limits**

- Release asset names must be plain file names. Archives from the github, http, aqua, cargo-binstall, packslip and vfox backends may not contain symlinks that point outside the install directory. [#&#8203;14895](https://redirect.github.com/jdx/mise/pull/14895) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Resolved versions, plugin names and task stub names are checked before they become paths or URL segments. A remote version containing `..`, quotes, `$` or a leading `-` now fails as an invalid tool version. [#&#8203;14901](https://redirect.github.com/jdx/mise/pull/14901) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Git lookups, build tools (ruby-build, python-build, `cargo install`, `gem install`) and npm now run outside the project directory, so the repo's `.git/config` and other project files can't change what they do. Untrusted values are kept out of their command-line arguments. In safe mode, asdf `exec-env` is refused. [#&#8203;14896](https://redirect.github.com/jdx/mise/pull/14896) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Committed symlinks around lockfiles, tool stubs and the remote task cache can no longer make mise read, write or delete files outside the repo. An untrusted lockfile still pins versions and checksums, but it can no longer switch a tool's backend, add inline options or supply download URLs. [#&#8203;14906](https://redirect.github.com/jdx/mise/pull/14906) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The task sandbox has fewer escapes:

  - `--allow-write` refuses project symlinks that lead outside the project;
  - `--deny-net` also blocks other socket families, io_uring and x32 syscalls;
  - an empty `allow_write` blocks all writes instead of turning the sandbox off;
  - macOS always uses `/usr/bin/sandbox-exec`;
  - OCI whiteouts can't delete through planted symlinks.

  [#&#8203;14911](https://redirect.github.com/jdx/mise/pull/14911) ([@&#8203;jdx](https://redirect.github.com/jdx))
- HTTP response bodies are capped at 128 MiB, and slow bodies are bounded by `http_download_timeout`. Downloads are saved exactly as sent. Archive extraction stops once the output exceeds both 256 MiB and 100 times the download size (`extract_max_ratio`). [#&#8203;14915](https://redirect.github.com/jdx/mise/pull/14915) ([@&#8203;jdx](https://redirect.github.com/jdx))

**Credentials**

- The GitHub token is sent only to GitHub API hosts or the configured `api_url`, not to any host that serves `/api/v3`. Release caches are keyed by the exact repository. A download that fails its pinned checksum is not kept. A project can no longer mark a task `global`. In safe mode, remote task files from project config are not fetched. [#&#8203;14913](https://redirect.github.com/jdx/mise/pull/14913) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Credentials are now kept out of install directory names, install manifests, `mise.lock`, `Referer` headers, logs and error messages. Age keys and Forgejo tokens are redacted in `mise doctor`, `mise settings` and `mise tool`. Shebang task scripts are readable only by their owner, and multi-line secrets are redacted in task output. [#&#8203;14917](https://redirect.github.com/jdx/mise/pull/14917) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- `uv_venv_auto` could fork-bomb the machine when uv probed mise's python shims while creating a venv. mise now skips venv creation when it is started from inside that venv creation. [#&#8203;14927](https://redirect.github.com/jdx/mise/pull/14927) ([@&#8203;jdx](https://redirect.github.com/jdx))
- With `minimum_release_age` set, `mise upgrade` no longer downgrades tools that use date versions with leading zeros (for example `2026.10.21` to `2026.08.19`). [#&#8203;14923](https://redirect.github.com/jdx/mise/pull/14923) ([@&#8203;jdx](https://redirect.github.com/jdx))
- File tasks that `extends` a task template now use the template's `usage` spec, so its flags show in `-h` and are accepted. [#&#8203;14888](https://redirect.github.com/jdx/mise/pull/14888) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `mise tasks add --file` now quotes and escapes `#MISE` header values. Before, `--dir` and values containing quotes produced invalid TOML that broke the next `mise run`. [#&#8203;14925](https://redirect.github.com/jdx/mise/pull/14925) ([@&#8203;xucian](https://redirect.github.com/xucian))
- `mise uninstall` can now remove installs whose directories deny their owner write access, such as read-only `0555` runtime trees. [#&#8203;14883](https://redirect.github.com/jdx/mise/pull/14883) ([@&#8203;HengYangDS](https://redirect.github.com/HengYangDS))
- A `pypi:` tool with a private `git+ssh://` github.com source now lists versions through `git ls-remote` over SSH when GitHub can't see the repository. [#&#8203;14905](https://redirect.github.com/jdx/mise/pull/14905) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;owpac](https://redirect.github.com/owpac))

#### Breaking Changes

Most setups need no changes. Check these cases:

- **Lockfiles:** installs now rewrite `mise.lock` in `generate` mode. To keep the old behavior, set `lockfile_mode = "merge"`.
- **Trust prompts:** `mise set`/`unset`/`tasks add` in a directory with an untrusted sibling config now ask for trust, or fail without a terminal. A project that has only a `mise-tasks/` directory needs `mise trust .` before its dynamic task completions work. In paranoid mode, run `mise trust <file>` once for each existing task file.
- **Worktrees and includes:** a worktree you moved by hand needs `git worktree repair` before it shares trust again. A remote include pinned to an annotated tag's object ID must pin the commit SHA instead. Your own `~/.miserc.toml` can no longer ignore the global config.
- **Project `.miserc.toml`:** templates that read `env` or use loops or functions other than `arch()`, `os()`, `os_family()`, `libc()`, `avx2()` and `num_cpus()` are no longer rendered. Move them to the global `.miserc.toml`.
- **Safe mode:** project templates that read an env var without a `default` now fail to render. Projects that point at a private forge or a netrc-protected server can no longer list versions there.
- **Node:** mirrors that don't copy the `.sig` files, and early releases without one (for example 4.0.0 and 5.12.0), need `node.gpg_verify = false`.
- **SLSA:** a `github:` tool that sets `slsa_signer_identity` but builds from a branch instead of a tag no longer verifies. A lock entry that has a checksum but no provenance installs on the checksum alone; re-run `mise lock` or set `locked_verify_provenance`.
- **packslip:** after a repository rename, a pin without a repository ID needs `mise packslip forget <tool>`.
- **GitHub Enterprise:** `http:` tools, aqua packages and remote tasks on `/api/v3` URLs no longer get the GitHub token automatically. Add the host to `github.enterprise_hosts` in global config, or set `api_url`.
- **Credentials in URLs:** `mise lock` refuses URLs whose query has a credential-like parameter. Use `headers` or netrc instead. Tools installed under a directory name that contained credentials are reinstalled under a clean name.
- **Task sandbox:** `--deny-write`/`allow_write` sandboxes no longer start on Linux before 6.2. Programs that need io_uring no longer run under `--deny-net`. To allow writes through a project symlink that points outside the project, name its target instead.
- **Archives:** archives that contain symlinks pointing outside the install directory fail to install (brew casks and aqua dmg/pkg are exempt). Archives that legitimately expand more than 100 times and past 256 MiB need a higher `extract_max_ratio`, or `0` to turn the check off.
- **Hooks:** a project's `enter`, `leave` and `watch_files` hooks now start in the project root. Use `$MISE_PROJECT_ROOT` if a hook needs the root explicitly.
- **Other changes:**
  - `MISE_OVERRIDE_*_FILENAMES` entries are matched as literal file names;
  - config, lock and task files over 16 MiB are refused;
  - a `.tool-versions` or idiomatic version file that links to a file with a different name is skipped;
  - a relative `go.repo` resolves from `$HOME`;
  - a bare `OWNER/REPO` passed to `bootstrap --adopt` that matches a local path is an error (write `./OWNER/REPO` for the local path).

#### New Contributors

- [@&#8203;xucian](https://redirect.github.com/xucian) made their first contribution in [#&#8203;14925](https://redirect.github.com/jdx/mise/pull/14925)
- [@&#8203;HengYangDS](https://redirect.github.com/HengYangDS) made their first contribution in [#&#8203;14883](https://redirect.github.com/jdx/mise/pull/14883)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.18...v2026.10.19>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.18`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.18): : mise lsp: a language server for mise.toml

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.17...vfox-v2026.10.18)

mise now includes a language server, `mise lsp`, which gives editors diagnostics, completion, hover, navigation and quick fixes for `mise.toml`. Doctor checks can depend on other checks, and `mise dot history purge` erases a tracked file from dotfiles history. This release also fixes many lockfile, prune, Windows and dotfiles issues.

#### Added

- **`mise lsp`, a language server for mise config files.** It runs over stdin/stdout, so any editor with an LSP client can use it (Neovim, Helix, Emacs, Zed, VS Code, ...). It uses the schema and formatter of the installed mise, so its answers match your version. [#&#8203;14744](https://redirect.github.com/jdx/mise/pull/14744), [#&#8203;14808](https://redirect.github.com/jdx/mise/pull/14808), [#&#8203;14811](https://redirect.github.com/jdx/mise/pull/14811), [#&#8203;14812](https://redirect.github.com/jdx/mise/pull/14812) ([@&#8203;jdx](https://redirect.github.com/jdx))

  - **Diagnostics:** TOML syntax errors, unknown keys with a "did you mean" suggestion, values of the wrong type, and deprecated keys. It warns about task references that point to no task and reports `depends` cycles as errors.
  - **Completion and hover:** config keys and enum or boolean values, task names in `depends`, `depends_post`, `wait_for`, `run = [{ task = ... }]` and `extends`, and tool names from the registry under `[tools]`. Hover shows the description and backends of a tool, or the description and command of a task.
  - **Navigation and editing:** go to a task's definition (also in included files), find its references, and rename a task together with every reference in the file. Quick fixes replace an unknown key or task name, and rewrite a deprecated key such as `env_path` to the key that replaces it. Formatting gives the same output as `mise fmt`.
  - It only handles files that mise loads as configs (`mise.toml`, `mise.<env>.toml`, `conf.d/*.toml`, ...) and ignores `Cargo.toml`, `pyproject.toml` and other TOML files, so you can attach it to the TOML filetype. Configs are parsed but never rendered, and it makes no network requests, so opening an untrusted file runs nothing. Clients can turn off `formatting`, `hover`, `completion`, `definition`, `references` or `rename` with `initializationOptions`.

  ```lua
  vim.lsp.config("mise", { cmd = { "mise", "lsp" }, filetypes = { "toml" }, root_markers = { "mise.toml" } })
  vim.lsp.enable("mise")
  ```

- **Doctor checks can depend on other checks.** Add `depends` (a single name or a list) to a check in `[doctor.checks.*]`. If a dependency fails, errors, is skipped or is blocked, the dependent check is reported as `blocked`, and its command and hint are skipped. A `blocked` result does not change the exit code on its own. An unknown dependency name or a cycle is reported as a config error, and then no checks run. [#&#8203;14773](https://redirect.github.com/jdx/mise/pull/14773) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [doctor.checks."examplectl:project"]
  depends = ["examplectl:auth"]
  run = 'test -n "$(examplectl config get project)"'
  ```

- **`mise dot history purge <path>` removes a tracked path from local dotfiles history.** Use it after tracking a secret by mistake. Checkpoints that changed only that path are removed. All other checkpoints keep their numeric IDs, messages and dates, so `history`, `rollback` and `undo` keep working. Use `--dry-run` to preview and `--yes` to skip the prompt. `--backup` keeps the old history under `refs/mise/purge-backup/`, and `--keep-objects` skips pruning. The command refuses if any version of the path is already reachable from the origin, because mise never force-pushes. [#&#8203;14831](https://redirect.github.com/jdx/mise/pull/14831) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **A dotfiles variant's `target` can be a template**, so one shared config can link into locations that differ between machines. Only the variant `target` is rendered. Templates get the standard config context (`env`, `xdg_config_home`, `config_root`, ...), but `exec()` and `vars` are not available. The rendered path must be absolute or start with `~/`. [#&#8203;14856](https://redirect.github.com/jdx/mise/pull/14856) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;ponko2](https://redirect.github.com/ponko2))

  ```toml
  [dotfiles.examplectl]
  source = "dotfiles/examplectl"
  mode = "symlink"
  variants = [{ default = true, target = "{{ xdg_config_home }}/examplectl" }]
  ```

- **`mise upgrade --bump --pin` writes resolved versions to config.** Requests such as `latest` or `20` become the exact version, for example `24.14.0`, even if the tool was already up to date. With the `pin` setting (`MISE_PIN=1`), a plain `mise upgrade --bump` now does the same. `--dry-run` shows what would change. [#&#8203;14791](https://redirect.github.com/jdx/mise/pull/14791) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`[bootstrap.hooks]` tables accept `run_windows`**, as tasks and `[hooks]` already do. On Windows it is used instead of `run`. A table with only `run_windows` does nothing on other platforms. [#&#8203;14851](https://redirect.github.com/jdx/mise/pull/14851) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **Registry:** `goose-agent` (`aqua:aaif-goose/goose`), the Goose AI agent CLI. The installed binary is still named `goose`. [#&#8203;14822](https://redirect.github.com/jdx/mise/pull/14822) ([@&#8203;kwsantiago](https://redirect.github.com/kwsantiago))

#### Changed

- **`pin` now also affects a plain `mise upgrade --bump`.** If you have `pin` enabled, your next `mise upgrade --bump` rewrites non-exact requests such as `"22"` to exact versions such as `"22.1.0"`. Without `pin`, requests keep their precision as before. [#&#8203;14791](https://redirect.github.com/jdx/mise/pull/14791) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **`mise unuse tool@version` only prunes the version you named.** It used to prune every unused version of the tool. If the named version is kept, mise now explains why, for example `[email protected].0 is kept: required by ~/proj/mise.toml`. `mise unuse node` without a version still prunes all unused versions of node. [#&#8203;14863](https://redirect.github.com/jdx/mise/pull/14863) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;Strepto](https://redirect.github.com/Strepto))

#### Fixed

##### Lockfiles

- If a pipx tool selects its own Python (`python = "3.12"`), `mise use`, `mise up -l`, `mise lock pipx:...` and `mise lock python` now keep both that Python and the project's Python in `mise.lock`. Before, one entry was dropped and `mise install --locked` failed. [#&#8203;14794](https://redirect.github.com/jdx/mise/pull/14794) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;AllDmeat](https://redirect.github.com/AllDmeat))
- `mise install tool@latest` no longer fails with "lockfile contains multiple resolutions" when nested configs pin different versions. The nearest lockfile now decides. [#&#8203;14818](https://redirect.github.com/jdx/mise/pull/14818) ([@&#8203;jdx](https://redirect.github.com/jdx))
- When a monorepo root config is in `.config/mise.toml`, the shared lockfile is now written to `.config/mise.lock` instead of `<root>/mise.lock`. [#&#8203;14860](https://redirect.github.com/jdx/mise/pull/14860) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;fxsalazar](https://redirect.github.com/fxsalazar))
- `mise lock` for `pipx:` and `pypi:` tools now accepts PyPI simple indexes that respond with a PEP 691 `+html` content type, such as Azure Artifacts. [#&#8203;14852](https://redirect.github.com/jdx/mise/pull/14852) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;alexanderlinne](https://redirect.github.com/alexanderlinne))
- Locked conda installs reuse cached downloads whose checksum is known, instead of downloading the package again. [#&#8203;14866](https://redirect.github.com/jdx/mise/pull/14866) ([@&#8203;zeitlinger](https://redirect.github.com/zeitlinger))

##### Install, upgrade and prune

- A project-level `[hooks].postinstall` now runs with the version that was just installed. Before, during `mise upgrade` of a fuzzy request such as `latest`, its `PATH` pointed at the old version. [#&#8203;14853](https://redirect.github.com/jdx/mise/pull/14853) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;sam9191](https://redirect.github.com/sam9191))
- `mise prune` no longer aborts with "points to an unknown mise Python installation" when a pypi/pipx tool's receipt reaches mise's Python through a minor-version link such as `python/3.14`. [#&#8203;14857](https://redirect.github.com/jdx/mise/pull/14857), [#&#8203;14874](https://redirect.github.com/jdx/mise/pull/14874) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;chancez](https://redirect.github.com/chancez))
- With `MISE_OVERRIDE_TOOL_VERSIONS_FILENAMES` set, tracked `.tool-versions` files are still loaded. Before, `mise prune` could remove versions that those projects still used. [#&#8203;14779](https://redirect.github.com/jdx/mise/pull/14779) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `java = "latest"` now selects the newest installed build when two installed versions differ only after `+` (for example `+7` and `+101`). [#&#8203;14876](https://redirect.github.com/jdx/mise/pull/14876) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;javabrett](https://redirect.github.com/javabrett))
- `gem:` installs and `bundle install` no longer fail when `mise` is not on `PATH`. This applies to Rubies installed with this release or later. [#&#8203;14850](https://redirect.github.com/jdx/mise/pull/14850) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;fxsalazar](https://redirect.github.com/fxsalazar))
- packslip tools no longer warn on every `mise outdated` or `mise up` about skipping a version that is past the `minimum_release_age` cutoff when that version is already installed. [#&#8203;14835](https://redirect.github.com/jdx/mise/pull/14835) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;alx-xo](https://redirect.github.com/alx-xo))
- Homebrew tap formulae that define their own `Requirement` classes no longer break `mise bootstrap packages apply`. mise does not enforce these requirements and logs a warning about them. [#&#8203;14807](https://redirect.github.com/jdx/mise/pull/14807) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `mise oci build` images that include `rust` now contain a working `cargo` and `rustc`, with `CARGO_HOME` and `RUSTUP_HOME` set, so containers no longer download the toolchain again on first use. The build now fails if rust comes from a rustup that mise does not manage. [#&#8203;14854](https://redirect.github.com/jdx/mise/pull/14854) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;eiswind](https://redirect.github.com/eiswind))

##### Network and backends

- In official rustls Linux builds, mise falls back to its bundled Mozilla root certificates, with a warning, when no system CA certificates can be loaded (for example in minimal containers, or when `SSL_CERT_FILE` points to a missing file). Roots that do load are not changed. [#&#8203;14817](https://redirect.github.com/jdx/mise/pull/14817) ([@&#8203;jdx](https://redirect.github.com/jdx))
- GitHub assets now download correctly when `.netrc` has credentials for `github.com`. The reachability check no longer sends those credentials, which had caused a fallback to the rate-limited API. [#&#8203;14786](https://redirect.github.com/jdx/mise/pull/14786) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `github:` tools for which mise-versions returned an empty release list (such as `github:caarlos0/svu`) now fall back to paged listing, so their versions are found. [#&#8203;14877](https://redirect.github.com/jdx/mise/pull/14877) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Expired glab OAuth2 tokens are now ignored instead of sent. This fixes `401 Unauthorized` errors when installing `gitlab:` tools from public projects. [#&#8203;14873](https://redirect.github.com/jdx/mise/pull/14873) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;chung1905](https://redirect.github.com/chung1905))

##### Windows

- Zip archives that contain read-only files, such as the Flutter SDK, now extract without "Access is denied". [#&#8203;14869](https://redirect.github.com/jdx/mise/pull/14869) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;akiWagashi](https://redirect.github.com/akiWagashi))
- uv-locked `pipx:` tools whose console script names end in `.py` (for example `pipx:clang-format`) now install. [#&#8203;14825](https://redirect.github.com/jdx/mise/pull/14825) ([@&#8203;jdx](https://redirect.github.com/jdx))
- In tasks, `run_windows = []` now skips the task on Windows instead of running `run`. [#&#8203;14859](https://redirect.github.com/jdx/mise/pull/14859) ([@&#8203;jdx](https://redirect.github.com/jdx))

##### Tasks, bootstrap and generate

- `mise tasks list` now works as an alias for `mise tasks ls`. If you have a task named `list`, that task still runs. [#&#8203;14878](https://redirect.github.com/jdx/mise/pull/14878) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;mcstafford-git](https://redirect.github.com/mcstafford-git))
- `mise bootstrap` no longer fails when a namespaced task such as `system:bootstrap` exists but no task is named exactly `bootstrap`. [#&#8203;14858](https://redirect.github.com/jdx/mise/pull/14858) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `mise generate install-script --write` and `mise generate task-stubs` now write files that end with a newline. Existing stubs regenerate as before. [#&#8203;14819](https://redirect.github.com/jdx/mise/pull/14819) ([@&#8203;itochan](https://redirect.github.com/itochan))
- `curl https://mise.run | sh` no longer fails when `~/.config/curlrc` sets `write-out`. [#&#8203;14830](https://redirect.github.com/jdx/mise/pull/14830) ([@&#8203;jdx](https://redirect.github.com/jdx))

##### Dotfiles

- A `mode = "track"` entry declared in a `config.<env>.toml` stays tracked on machines that don't select that env. Before, those machines published a removal of the entry. [#&#8203;14815](https://redirect.github.com/jdx/mise/pull/14815) ([@&#8203;jdx](https://redirect.github.com/jdx))
- One saved enrollment under a parent directory that is now a symlink no longer stops automatic capture for all other tracked paths. It is reported as invalid instead. [#&#8203;14871](https://redirect.github.com/jdx/mise/pull/14871) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The JSON schema now declares the keys of `[dotfile_groups.<name>.entries]`, so strict validators such as Tombi accept valid entries and report unknown keys. [#&#8203;14810](https://redirect.github.com/jdx/mise/pull/14810) ([@&#8203;jdx](https://redirect.github.com/jdx))

##### Other

- The aqua registry built into mise is now encoded the same way on every build, so builds of the same source are reproducible. [#&#8203;14809](https://redirect.github.com/jdx/mise/pull/14809) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Performance

- mise now checks `trusted_config_paths` after the cheaper trust rules and resolves each entry at most once per process. This speeds up `hook-env` when entries are on slow mounts such as autofs `/home` on macOS. [#&#8203;14875](https://redirect.github.com/jdx/mise/pull/14875) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Documentation

- The dotfiles docs now explain repo-only files, how existing repos fit in, undoing an accidental track, and what `add --no-apply`, `dot diff` and `exclude` do. [#&#8203;14829](https://redirect.github.com/jdx/mise/pull/14829), [#&#8203;14862](https://redirect.github.com/jdx/mise/pull/14862) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### New Contributors

- [@&#8203;kwsantiago](https://redirect.github.com/kwsantiago) made their first contribution in [#&#8203;14822](https://redirect.github.com/jdx/mise/pull/14822)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.17...v2026.10.18>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.17`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.17): : Shell names in bootstrap login_shell, templated doctor checks, and a per-plugin vfox cache directory

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.15...vfox-v2026.10.17)

`bootstrap.user.login_shell` now accepts a shell name such as `fish` instead of a full path. `[doctor.checks]` can use templates, and vfox backend plugins get their own cache directory. This release also fixes PATH handling in zsh setups that deduplicate PATH, stale task cache hits, a pipx interpreter mix-up on macOS, and removes trust in the compromised eMudhra CA certificates.

It also includes the changes prepared for 2026.10.16, which was tagged but never published: relative and templated `write_targets` paths, version aliases from vfox backend plugins, keyring passwords for private PyPI indexes, `vfox-backend:` packslip plugin sources, and a change to where `find_up` starts (see Changed and Upgrading).

#### Added

- **`[bootstrap.user].login_shell` accepts a shell name.** A config shared across machines no longer needs to know where each system installs the shell. mise looks up the name when it checks or applies the setting, which happens after `bootstrap.packages` has installed the shell. It searches `PATH` first (never the mise shims directory), then common package-manager locations: `/opt/homebrew/bin`, `/usr/local/bin`, `/home/linuxbrew/.linuxbrew/bin`, `~/.linuxbrew/bin`, `/usr/bin` and `/bin`. If no executable is found, `mise bootstrap user apply` fails without changing `/etc/shells` or the account, and `status` reports `not installed`. `mise bootstrap user status` shows the path it found, and `--json` output has a new `resolved` field. Absolute paths work as before. [#&#8203;14770](https://redirect.github.com/jdx/mise/pull/14770) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.packages]
  "brew:fish" = "latest"

  [bootstrap.user]
  login_shell = "fish"
  ```

- **Doctor checks support templates.** `run`, `description` and `hint` in `[doctor.checks.*]` are rendered with the template context of the config file that declares them, the same way hooks are. A check can now use a value from `[vars]` instead of hard-coding it. `dir` is not rendered. If a template fails, only that check reports `ERROR` and the other checks still run. Text that already contains `{{` or `{%` in these fields is now parsed as a template. [#&#8203;14771](https://redirect.github.com/jdx/mise/pull/14771) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [doctor.checks."project-a:env"]
  description = "project-a has a .env file"
  run = 'test -f {{ vars.repos_dir | quote }}/project-a/.env'
  hint = "cp {{ vars.repos_dir }}/project-a/.env.example {{ vars.repos_dir }}/project-a/.env"
  ```

- **vfox plugins get `RUNTIME.cacheDirPath`.** This is a cache directory for each plugin at `$MISE_CACHE_DIR/plugins/<plugin-name>`. mise creates it before running any hook, and every hook of the plugin sees the same path. Unlike `pluginDirPath`, it is kept when the plugin is reinstalled. `mise cache clear` deletes it, so plugins must handle it being missing. It is `nil` when no cache directory is available. [#&#8203;14776](https://redirect.github.com/jdx/mise/pull/14776) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`settings.write_targets` accepts relative and templated paths.** Before, only absolute or `~/` paths worked, so dotfiles shared between machines with different `MISE_CONFIG_DIR` or `XDG_CONFIG_HOME` had to hardcode the config directory. Templates are now rendered, and a relative result is joined to the `config_root` of the settings file that declares it. For a global file that is `$HOME`, so the portable spelling uses the file's own directory. The resolved path must still be a global config file that mise loads. If a template fails to render, mise warns, and `mise use --global` fails with "must resolve to an absolute path" instead of writing to the default file. [#&#8203;14742](https://redirect.github.com/jdx/mise/pull/14742) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  # ~/.config/mise/conf.d/settings.toml
  [settings.write_targets]
  tools = "{{ config_source | dirname }}/tools.toml"
  ```

- **vfox backend plugins can report version aliases.** `BackendListVersions` can return an `aliases` table, so a registry's moving names resolve to real versions. Before, `"mise-ghcup:ghc" = "recommended"` passed the literal string to the plugin, installed into `installs/.../recommended`, never followed the tag when it moved, and showed `recommended` in `mise ls` and the lockfile. Now `ghc@recommended` resolves to `9.10.3`, installs into `9.10.3/`, and the lockfile records `9.10.3`. Aliases you define in config still win. They are cached like the version list, and an expired cache still answers offline. Older mise versions ignore the `aliases` field, so plugins can adopt it right away. [#&#8203;14732](https://redirect.github.com/jdx/mise/pull/14732) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;cprecioso](https://redirect.github.com/cprecioso))

  ```lua
  return {
    versions = { "9.8.4", "9.10.3", "9.14.1" },
    aliases = { recommended = "9.10.3" },
  }
  ```

- **`mise lock` and version listing work with a PyPI index that uses a keyring.** With an index such as Azure Artifacts and `artifacts-keyring`, `mise install` worked because uv read the password from its keyring provider, but mise's own requests sent no credentials and failed with `401 Unauthorized`. Set the new `pypi.keyring_provider` (`disabled` by default, or `subprocess`; env var `MISE_PYPI_KEYRING_PROVIDER`) and give the registry URL a username without a password. mise then runs `keyring get <url> <username>`, as uv does, and sends the result as HTTP Basic credentials when it lists versions and when `mise lock` reads a release's `requires-python`. The password is never written to the lockfile or the version cache. The `keyring` executable must be on `PATH`. If it is missing or has no entry, mise sends the request without credentials. [#&#8203;14733](https://redirect.github.com/jdx/mise/pull/14733) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [settings.pypi]
  keyring_provider = "subprocess"
  registry_url = "https://[email protected]/org/project/_packaging/feed/pypi/simple/{}/"
  ```

- **`vfox-backend:` works as a prefix for packslip plugin sources.** A vfox backend plugin can be installed from a signed packslip archive, both from the CLI and in `[plugins]`. Before, `mise plugins install vfox-backend:foo 'packslip:owner/repo#1.0.0'` failed with `packslip plugin sources require the vfox plugin type`. An explicit `asdf:` or `package:` prefix is still rejected, and the error now says "vfox or vfox-backend". The archive still declares `extensions.mise.plugin = "vfox"`. [#&#8203;14727](https://redirect.github.com/jdx/mise/pull/14727) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise plugins install vfox-backend:my-backend 'packslip:your-org/my-backend#1.0.0'
  ```

- `mise bootstrap packages list` is now an alias for `status`, alongside `ls`. [#&#8203;14709](https://redirect.github.com/jdx/mise/pull/14709) ([@&#8203;himkt](https://redirect.github.com/himkt))

#### Changed

- **`find_up` in `env._.file` starts at the config root.** Every other relative path in a config resolves against the config root, but `find_up` started at the current directory. A relative `find_up` path now starts at the config root and walks up through its parents, which suits a monorepo whose root `.env` applies to every package. An absolute path starts at its own directory. A global config that wants to search from wherever mise runs can use `cwd`. The `env_file` setting still discovers dotenv files up from the current directory and is unchanged. See Upgrading. [#&#8203;14734](https://redirect.github.com/jdx/mise/pull/14734) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))

  ```toml
  # ~/.config/mise/config.toml
  [env]
  _.file = { path = "{{cwd}}/.env", find_up = true }
  ```

- **`allow_builds` approves only the packages it lists.** With the embedded aube installer, `allow_builds = ["re2"]` also ran the install scripts of every package on aube's bundled trusted-dependencies list, such as `dtrace-provider`. For a package list, mise now turns that default list off, so only the packages you name build. `allow_builds = true` and the default with no option are unchanged. This updates the embedded aube to 2.8.0. [#&#8203;14731](https://redirect.github.com/jdx/mise/pull/14731) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- `exec()` in `[env]` values, and other commands mise runs with the pre-activation environment, can now find commands in a `_.path` directory that was already on `PATH` before activation, even if the shell deduplicates `PATH` (for example zsh with `typeset -U path PATH`). [#&#8203;14772](https://redirect.github.com/jdx/mise/pull/14772) ([@&#8203;jdx](https://redirect.github.com/jdx))
- With `task.source_freshness_hash_contents = true`, a task now re-runs when a source file is changed to content of the same size and its old mtime is restored (for example by `rsync -a`, `cp -p` or `touch -r`). On Unix, mise now also compares the file's ctime. Cache entries written by older versions are rehashed once. [#&#8203;14778](https://redirect.github.com/jdx/mise/pull/14778) ([@&#8203;jdx](https://redirect.github.com/jdx))
- On macOS, `pipx:` installs no longer fail when `aqua:aws/aws-cli` is in the toolset. Because macOS filesystems ignore case by default, mise's lookup for `python` matched aws-cli's executable `Python` shared library and passed it to uv as the interpreter. This lookup now requires the exact case. [#&#8203;14769](https://redirect.github.com/jdx/mise/pull/14769) ([@&#8203;jdx](https://redirect.github.com/jdx))
- An aqua registry template with an unterminated string literal now fails with an "unterminated string literal" error instead of hanging. Escaped quotes after backslashes are also detected correctly. [#&#8203;14752](https://redirect.github.com/jdx/mise/pull/14752) ([@&#8203;Huge](https://redirect.github.com/Huge), [@&#8203;jdx](https://redirect.github.com/jdx))
- The built-in npm backend no longer trusts the eight eMudhra intermediate CA certificates whose private keys were compromised. The fix comes from updating its bundled root certificates (`webpki-root-certs` 1.0.10, `rustls-webpki` 0.103.16). Builds that use the OS trust store are fixed by OS updates instead. [#&#8203;14781](https://redirect.github.com/jdx/mise/pull/14781) ([@&#8203;jdx](https://redirect.github.com/jdx))
- A `[bootstrap.compose]` project no longer reports drift on every run when a service uses a `service:` reference such as `network_mode: service:vpn`. Compose hashes the reference as written but stamps the container with the hash of the resolved `container:<id>`, so the two never matched. mise now pins these references (`network_mode`, `ipc`, `pid`, `volumes_from`) to the running container before it asks compose for the hash. This needs an explicit `files` list in the project's config. Projects without one hash as before. [#&#8203;14729](https://redirect.github.com/jdx/mise/pull/14729) ([@&#8203;jdx](https://redirect.github.com/jdx))
- vfox plugins now keep symlinks when they extract `.zip` archives returned by `PreInstall`. Before, entries such as `bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js` became regular files holding the link target, and the tool failed with `No such file or directory`. Links that resolve outside the install directory are still rejected. The `http:` backend and tarballs were not affected. [#&#8203;14730](https://redirect.github.com/jdx/mise/pull/14730) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `[daemons.<name>] task = "worker"` now finds a file task such as `.mise/tasks/worker.sh`, as `mise run worker` does. Before, it failed with `daemon srv runs unknown task "worker"` unless you wrote `worker.sh`. [#&#8203;14741](https://redirect.github.com/jdx/mise/pull/14741) ([@&#8203;jdx](https://redirect.github.com/jdx))
- On Windows, `mise self-update` no longer replaces the winget `mise-shim.exe` link with a copy. It updates the real `mise-shim.exe` next to the real `mise.exe`. Before, `mise reshim` kept warning that the shim was older than mise. [#&#8203;14737](https://redirect.github.com/jdx/mise/pull/14737) ([@&#8203;jdx](https://redirect.github.com/jdx))
- An inherited `[dotfiles]` entry turned off with only `enabled = false` no longer prints `no recognized operation (block, source, or line), ignoring entry` on every command. Other tables with no recognized operation, such as a misspelled `line`, still warn. [#&#8203;14726](https://redirect.github.com/jdx/mise/pull/14726) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;ponko2](https://redirect.github.com/ponko2))

#### Upgrading

- **`find_up` in a global config:** a relative `find_up` entry in `env._.file` used to search from the current directory. It now searches from the config root, which is `~` for a global config, so it no longer finds project files. To keep the old behavior, put `{{cwd}}/` in front of the path. `find_up` was added in 2026.10.12.

#### Documentation

- The dotfiles docs now explain that a group entry with only `permissions` does not inherit the group's `mode`, so it deploys nothing. Removing `mode` from an entry that used to deploy a file leaves that file orphaned, and `--prune` deletes it. To deploy a file and set its permissions, use `{ mode = "copy", permissions = "0600" }`. [#&#8203;14768](https://redirect.github.com/jdx/mise/pull/14768) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### New Contributors

- [@&#8203;Huge](https://redirect.github.com/Huge) made their first contribution in [#&#8203;14752](https://redirect.github.com/jdx/mise/pull/14752)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.15...v2026.10.17>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-26 08:58:43 -07:00
renovate[bot] 7e17be12d5 chore(deps): update yarn to v4.18.1 (#727)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [yarn](https://redirect.github.com/yarnpkg/berry) ([source](https://redirect.github.com/yarnpkg/berry/tree/HEAD/packages/yarnpkg-cli)) | [`4.13.0+sha512.5c20ba010c99815433e5c8453112165e673f1c7948d8d2b267f4b5e52097538658388ebc9f9580656d9b75c5cc996f990f611f99304a2197d4c56d21eea370e7` → `4.18.1`](https://renovatebot.com/diffs/npm/yarn/4.13.0/4.18.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@yarnpkg%2fcli-dist/4.18.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@yarnpkg%2fcli-dist/4.13.0/4.18.1?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:50:40 -07:00
renovate[bot] d24581ab27 chore(deps): update dependency jdx/mise to v2026.10.15 (#723)
> [!NOTE]
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jdx/mise](https://redirect.github.com/jdx/mise) | patch | `v2026.10.8` → `v2026.10.15` |

---

### Release Notes

<details>
<summary>jdx/mise</summary>

### [`v2026.10.15`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.15): : Default minimum_release_age covers every backend, plus a PowerShell installer

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.14...vfox-v2026.10.15)

The default 24-hour `minimum_release_age` now applies to every backend. Many more backends report release dates, so the cooldown can actually hold new versions back, and mise warns when it installs a version it couldn't date. This release also adds a PowerShell installer for Windows, `read_json`/`read_yaml`/`read_toml` template functions, `dotnet@lts`/`dotnet@sts`, and new dotfiles options.

#### Changed

- **The default `minimum_release_age` applies to every backend.** The built-in 24h cooldown used to cover only backends that reported release dates (aqua, cargo, core, github, npm, pypi and others). asdf, vfox, conda, dotnet, `http:` and `s3:` tools had no cutoff at all. Now every backend gets the cooldown. A tool whose only version is less than 24h old now resolves to "no versions found", as aqua and github tools already did. To opt a tool out, add it to `minimum_release_age_excludes`. To turn the cooldown off, set `minimum_release_age = "0s"`. [#&#8203;14701](https://redirect.github.com/jdx/mise/pull/14701) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **mise warns when it can't check a version's release date.** A version with no known date still passes the cutoff, but `mise install`, `mise upgrade` and `mise use` now print a one-time warning naming the version. Exact pins you wrote yourself don't trigger it. `mise env`, `mise ls`, `mise outdated` and `mise ls-remote` never print it. [#&#8203;14694](https://redirect.github.com/jdx/mise/pull/14694) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```
  WARN  asdf:[email protected] has no release date, so the ... cutoff cannot be checked and it is allowed (exclude it with minimum_release_age_excludes to silence this)
  ```

- **More backends report release dates, so the cooldown applies to them:**

  - conda versions use the channel's earliest index timestamp. [#&#8203;14693](https://redirect.github.com/jdx/mise/pull/14693)
  - aqua packages that take versions from git tags (`version_source: github_tag`, such as `aws/aws-cli`, `docker/cli` and `cockroachdb/cockroach`) are now dated. [#&#8203;14691](https://redirect.github.com/jdx/mise/pull/14691)
  - dotnet dates come from Microsoft's releases index. deno and erlang dates come from their GitHub releases. [#&#8203;14692](https://redirect.github.com/jdx/mise/pull/14692)
  - Every python version now has a date, and so do many more ruby versions (from ruby-lang.org, Maven Central for JRuby, and GitHub for TruffleRuby). Rust's `stable`, `beta` and `nightly` channels are dated too. With an explicit past cutoff, `rust@stable` resolves to the newest stable release that is old enough, and `beta`/`nightly` resolve to a dated build from before the cutoff. [#&#8203;14697](https://redirect.github.com/jdx/mise/pull/14697)
  - The dart, flutter, nim, julia, nomad-pack, sentinel, tfc-agent, android-cli, cursor-agent and twg registry tools now report dates. [#&#8203;14698](https://redirect.github.com/jdx/mise/pull/14698)

  All by [@&#8203;jdx](https://redirect.github.com/jdx).

- **Version lists for more backends come from the mise-versions host.** `npm:`, `pipx:`, `cargo:`, `gem:`, `go:`, `dotnet:` and `spm:` tools now list versions from mise-versions, like other registry tools, instead of going to their own source. So do http/s3 tools with a `version_list_url`. If you redirect a source, mise still lists from that source directly. That covers a custom npm registry or `npm.shell_out`, `pypi.registry_url`, a non-default `GOPROXY`, `cargo.registry_name`, a custom gem source, `dotnet.registry_url`, S3 endpoint overrides, and local listing options. conda always lists from its channel. The host cache TTL now follows the tool's `minimum_release_age`. To list everything from the source, set `MISE_USE_VERSIONS_HOST=0`. [#&#8203;14700](https://redirect.github.com/jdx/mise/pull/14700) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Added

- **PowerShell installer for Windows.** It's published with each release as `install.ps1` and served from mise.run at `/powershell` and `/pwsh`, so you pipe it into `iex` with `irm`. It works on PowerShell 5.1+ and doesn't need admin rights. It installs the newest release that is at least 24h old, or `MISE_VERSION` if set. The download is verified against `SHASUMS256.txt` from GitHub Releases. `mise.exe` and `mise-shim.exe` go into `~\.local\bin`, and that directory is added to your user `Path`. Re-running it upgrades an existing install. Options include `MISE_INSTALL_PATH`, `MISE_INSTALL_MODIFY_PATH=0`, `MISE_INSTALL_SHIMS_PATH=1` and `MISE_INSTALL_FROM_GITHUB=1`. See the "Installing mise" docs for the full command. [#&#8203;14660](https://redirect.github.com/jdx/mise/pull/14660) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;lhs-12](https://redirect.github.com/lhs-12))

- **`read_json()`, `read_yaml()` and `read_toml()` template functions** parse a file so a template can use a value from it. This saves copying a version from `package.json` or `Pulumi.yaml` into `mise.toml` by hand. Relative paths resolve from the config file's directory. A missing key is an error unless you add `| default(value="...")`. Like `read_file()`, these functions need a trusted config, and they don't work in safe mode, `.miserc.toml` or OCI dotfile templates. [#&#8203;14705](https://redirect.github.com/jdx/mise/pull/14705) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;edahlseng](https://redirect.github.com/edahlseng))

  ```toml
  [tools]
  node = "{{ read_json(path='package.json')['volta']['node'] }}"
  pulumi = "{{ read_yaml(path='Pulumi.yaml')['requiredPulumiVersion'] }}"
  ```

- **`dotnet@lts` and `dotnet@sts`** resolve to the newest SDK in the current LTS or STS channel, using Microsoft's release index. Preview, go-live and end-of-life channels are skipped. A `lts` alias you define in `[tool_alias.dotnet.versions]` still takes precedence. Runtime-only installs still need an exact version. [#&#8203;14683](https://redirect.github.com/jdx/mise/pull/14683) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`mise bootstrap --prune-dotfiles`** removes dotfiles that are no longer deployed, the same way `mise dot apply --prune` does, so `mise bootstrap --upgrade --prune-dotfiles` handles everything in one run. The prune runs at the end of the dotfiles phase. It respects `--dry-run`, `--yes` and `--skip dotfiles`. Copies you edited are left in place. [#&#8203;14681](https://redirect.github.com/jdx/mise/pull/14681) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))

- **`dotfiles.windows_symlinks`** (default `false`) makes `symlink` and `symlink-each` create real file symlinks on Windows. With it on, apply fails if Windows refuses to create a link, instead of silently copying. To create symlinks, Windows needs Developer Mode or an elevated shell. If you turn the setting back off, `mise dot unapply` needs `--force` on entries that still hold links. [#&#8203;14682](https://redirect.github.com/jdx/mise/pull/14682) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [settings.dotfiles]
  windows_symlinks = true
  ```

- **`http:` tools can report release dates** with the new `version_date_expr` option. It maps versions to dates from the `version_list_url` response. Use `version_date_url` to read the dates from a different URL, and `version_date_next_expr` to follow paged listings (same host only). Dates can be RFC 3339, `YYYY-MM-DD` or HTTP dates. [#&#8203;14696](https://redirect.github.com/jdx/mise/pull/14696) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **vfox plugins can report release dates.** An `Available` or `BackendListVersions` hook can return `created_at` (RFC 3339) for each version. Plain version strings still work. [#&#8203;14695](https://redirect.github.com/jdx/mise/pull/14695) ([@&#8203;jdx](https://redirect.github.com/jdx))

- `mise tasks -a` is a short alias for `mise tasks --all`. [#&#8203;14674](https://redirect.github.com/jdx/mise/pull/14674) ([@&#8203;williamsjokvist](https://redirect.github.com/williamsjokvist))

#### Fixed

- `mise fmt` no longer breaks multiline inline tables (TOML 1.1). It used to pull comments out of the table and drop indentation. Now it leaves these tables exactly as written. [#&#8203;14670](https://redirect.github.com/jdx/mise/pull/14670) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;ponko2](https://redirect.github.com/ponko2))
- If one task's `dir` template fails to render, other task commands keep working. Before, every command failed, including `mise tasks validate`. Now `mise tasks validate` reports the broken task as `invalid-directory-template`, and only running that task fails. [#&#8203;14680](https://redirect.github.com/jdx/mise/pull/14680) ([@&#8203;jdx](https://redirect.github.com/jdx))
- aqua tools now find their binaries in macOS universal archives, such as fastfetch 2.70.0. [#&#8203;14665](https://redirect.github.com/jdx/mise/pull/14665) ([@&#8203;jdx](https://redirect.github.com/jdx))
- npm: lifecycle scripts approved by `allow_builds` now run even when `~/.npmrc` sets `ignore-scripts=true`. [#&#8203;14661](https://redirect.github.com/jdx/mise/pull/14661) ([@&#8203;jdx](https://redirect.github.com/jdx))
- npm: `mise lock` no longer fails when your aube config sets a different default lockfile, such as `pnpm-lock.yaml`. [#&#8203;14659](https://redirect.github.com/jdx/mise/pull/14659) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `.7z` tool archives now extract when mise's data directory path contains `..`, for example in a localized mise setup. [#&#8203;14657](https://redirect.github.com/jdx/mise/pull/14657) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;sevenc-nanashi](https://redirect.github.com/sevenc-nanashi))
- The `mise-shim.exe` in the Windows release now keeps its version tag, so `mise doctor` and `mise reshim --force` stop reporting it as outdated. The warning goes away after you update and run `mise reshim --force`. [#&#8203;14656](https://redirect.github.com/jdx/mise/pull/14656) ([@&#8203;jdx](https://redirect.github.com/jdx))
- On Windows, `symlink-each` dotfiles messages and dry-run previews now say the files were copied rather than symlinked. [#&#8203;14654](https://redirect.github.com/jdx/mise/pull/14654) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;lhs-12](https://redirect.github.com/lhs-12))
- Rust: with a `RUSTUP_DIST_SERVER` mirror configured and GitHub unreachable, mise now lists the `stable`, `beta` and `nightly` channels instead of failing, so `rust@stable` installs through the mirror. [#&#8203;14662](https://redirect.github.com/jdx/mise/pull/14662) ([@&#8203;jdx](https://redirect.github.com/jdx))
- On Windows, `http:` installs of large archives no longer fail with "Access is denied" while antivirus is scanning the extracted files. mise now retries the final move for up to about a minute. [#&#8203;14703](https://redirect.github.com/jdx/mise/pull/14703) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Documentation

- New [mise.toml playground](https://mise.jdx.dev/configuration/playground.html): a schema-aware editor with hover help, completion and error checking. You can share a config as a link. Every `mise.toml` example in the docs now shows hover help on its keys. [#&#8203;14673](https://redirect.github.com/jdx/mise/pull/14673), [#&#8203;14686](https://redirect.github.com/jdx/mise/pull/14686) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The [mise.toml reference](https://mise.jdx.dev/configuration/reference.html) is now a searchable key explorer grouped by topic. Each key shows its type, default and a real example. Nearly every key now has a description, and these also appear in `mise config explain`. [#&#8203;14687](https://redirect.github.com/jdx/mise/pull/14687), [#&#8203;14690](https://redirect.github.com/jdx/mise/pull/14690), [#&#8203;14688](https://redirect.github.com/jdx/mise/pull/14688) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The fnox secrets page now covers setup, CI and migration. The dotnet page explains how private feeds authenticate. [#&#8203;14675](https://redirect.github.com/jdx/mise/pull/14675), [#&#8203;14679](https://redirect.github.com/jdx/mise/pull/14679) ([@&#8203;jdx](https://redirect.github.com/jdx))

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.14...v2026.10.15>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.14`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.14): : Global override config, mise config explain, and stricter tool option checks

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.13...vfox-v2026.10.14)

A global `config.override.toml` can now take precedence over project config, and `mise config explain` describes each key in a config file. Paranoid mode can be turned on per category, and in that mode dotenv files need their own content-bound trust. Installs now fail when a tool has an option its backend doesn't read, which affects existing configs (see Changed).

#### Added

- **Global override files.** `~/.config/mise/config.override.toml`, and `config.<env>.override.toml` for each `MISE_ENV`, rank above every project file, including `mise.local.toml` and `mise.<env>.toml`. This covers `[env]`, `[tools]`, `[settings]`, `[tasks]`, hooks and the other sections. Anything the override file doesn't set still comes from the project. mise never writes to these files by default. To write to one, pass `--override` to `use`, `unuse`, `set`, `unset`, `settings set|add|unset` or `config get|set`. A tool declared in an override file resolves from the global `mise.lock` and is exempt from the project's `--locked` check. If `MISE_GLOBAL_CONFIG_FILE` is set, override files are not loaded. `mise config ls` shows a `Tier` column and `mise doctor` labels the files. [#&#8203;14610](https://redirect.github.com/jdx/mise/pull/14610) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  # ~/.config/mise/config.override.toml
  [env]
  AWS_PROFILE = "personal"

  [tools]
  node = "24"
  ```

- **`mise config explain [KEY]`** lists each key a config file sets, with its line number and its description from the schema. It flags unknown keys with a "did you mean" suggestion and marks deprecated keys. It reads the file as plain TOML, so it also works on a config mise refuses to load. Pass a key such as `tasks` to show only that section, `--json` for machine-readable output, and `--file`, `--global`, `--system` or `--override` to pick a file. [#&#8203;14642](https://redirect.github.com/jdx/mise/pull/14642) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **Paranoid mode can be turned on per category.** `paranoid` now accepts a list of `config`, `env_files`, `plugins` and `provenance` in addition to `true`/`false`. `MISE_PARANOID=config,env_files` also works. `true` still turns on all four categories. An unknown category name is an error. [#&#8203;14609](https://redirect.github.com/jdx/mise/pull/14609) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))

  ```toml
  [settings]
  paranoid = ["env_files"]
  ```

- **`env_file` takes several filenames**, for example `env_file = [".env", ".env.local"]` or `MISE_ENV_FILE=.env:.env.local`. The file nearest the current directory wins. Within one directory, a name later in the list wins. A single string still works. [#&#8203;14606](https://redirect.github.com/jdx/mise/pull/14606) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))

- **A `find_up` entry in `env._.file` can search several names together.** Give one entry a `path` list, and mise searches directory by directory. The closest directory wins, then the later name within a directory. With two separate entries, a `.env.local` in a parent directory still overrides the project's `.env`. The docs now explain this difference and how `env_file` differs from `_.file`. [#&#8203;14620](https://redirect.github.com/jdx/mise/pull/14620), [#&#8203;14602](https://redirect.github.com/jdx/mise/pull/14602) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [env]
  _.file = { path = [".env", ".env.local"], find_up = true }
  ```

- **`#MISE` headers in file tasks accept the same keys as `[tasks.<name>]`**, including `timeout`, `vars` and the `deny_*`/`allow_*` sandbox keys. Before, these keys were ignored with a warning. If one header value is invalid, mise warns and skips that key, and every other task still loads. An invalid `confirm` or `deny_*` value is still an error, so a typo can't remove a confirmation prompt or a sandbox restriction. Before, a template named with `extends` overrode a header's `silent`. Now the header's value wins. [#&#8203;14637](https://redirect.github.com/jdx/mise/pull/14637) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **Interactive `mise.toml` reference** in the docs at [configuration/reference](https://mise.jdx.dev/configuration/reference.html). It lists every key with its type, choices and default, and you can filter keys and link to them directly. Plain Markdown copies are also published for agents. [#&#8203;14641](https://redirect.github.com/jdx/mise/pull/14641) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Changed

- **Installs fail on tool options the backend doesn't read.** Before, such options were silently ignored. A typo or a key meant for another backend now stops the install, and the error names the option: `cargo:eza: option 'asset_pattern' is not used by the cargo backend`. Keys under `platforms` are checked too. So far this applies to the cargo, conda, gem, github, gitlab, forgejo, go, http, npm, packslip, pipx, s3, spinel, spm and ubi backends. aqua, plugin and core-language tools are not checked. The npm backend accepts the `checksum` option that comes from a `packageManager` field in `package.json`. [#&#8203;14629](https://redirect.github.com/jdx/mise/pull/14629), [#&#8203;14655](https://redirect.github.com/jdx/mise/pull/14655) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **Paranoid mode needs content-bound trust for env files.** In paranoid mode, files loaded through `_.file`, found by the `env_file` setting, or run by `_.source` each need their own trust. Editing one of these files revokes its trust. Before, trusting `mise.toml` also approved these files, and later edits went unchecked. Running `mise trust` with no path trusts the project's config and every env file it loads. The error names the file but prints none of its values. To exempt a file by path, add it to `trusted_config_paths` in your global config. With a category list, this check belongs to `env_files`. [#&#8203;14604](https://redirect.github.com/jdx/mise/pull/14604) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))
- **More specific config error messages.**
  - A warning about an unknown `mise.toml` key now gives the line and column, and suggests a close match, for example `mise.toml:5:1: settings.experimantal (did you mean experimental?)`. [#&#8203;14639](https://redirect.github.com/jdx/mise/pull/14639) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - A wrong value for a hook, a task `run`/`depends` entry or an `[env]` variable now says which forms are accepted, or names the unknown key. Before, the error was `data did not match any variant of untagged enum`. An unknown key in a `run` or `depends` table now gets a warning. Before, it was silently ignored. [#&#8203;14640](https://redirect.github.com/jdx/mise/pull/14640) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **`[bootstrap.linux.firewall]` is validated on every OS.** macOS and Windows still skip the section at bootstrap time, but an invalid value such as `backend = "iptables"` is now an error there too. [#&#8203;14615](https://redirect.github.com/jdx/mise/pull/14615) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **Editor schema updates.** Editors now flag unknown keys in most `[bootstrap]` tables and in `[dotfiles]` entries. The schema now documents launchd `keep_alive_on_failure` and `nice`, and dotfile `autosave` and `enabled`. It also accepts valid configs it used to reject: templated `login_shell`, `queue_directories` and `umask`, nested macOS defaults values, and `tools` on `[vars]` entries. On Windows, `[bootstrap.brew].adopt` no longer causes an unknown-field warning. [#&#8203;14616](https://redirect.github.com/jdx/mise/pull/14616), [#&#8203;14644](https://redirect.github.com/jdx/mise/pull/14644), [#&#8203;14613](https://redirect.github.com/jdx/mise/pull/14613) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `mise dot status` and `mise dot sync` are faster with many tracked files. They start one git process per tracked file instead of three, which took about 10s down to 4s for 860 files. [#&#8203;14607](https://redirect.github.com/jdx/mise/pull/14607) ([@&#8203;waynehoover](https://redirect.github.com/waynehoover))

#### Fixed

- With an external rustup that keeps its proxies in a shared directory, such as pacman's `/usr/bin`, mise no longer puts that directory ahead of its own tools. Before, `mise exec -- node` could run the system Node. Only the Rust proxies are now exposed, through a directory that mise owns. [#&#8203;14597](https://redirect.github.com/jdx/mise/pull/14597) ([@&#8203;jdx](https://redirect.github.com/jdx))
- The cargo backend now honors `rename_exe`. Before, it ignored the option and installed the executable under its original name. [#&#8203;14626](https://redirect.github.com/jdx/mise/pull/14626) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Locked `pypi:` installs through uv now pass the configured mise Python to `uv sync`. Before, mise passed an empty path, so uv could pick another interpreter and fail on `--no-build` tools such as `yamllint`. [#&#8203;14593](https://redirect.github.com/jdx/mise/pull/14593) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;hollow](https://redirect.github.com/hollow))
- When the TLS root store or a CA file can't be loaded, the npm backend now reports an error instead of crashing mise with a core dump. [#&#8203;14595](https://redirect.github.com/jdx/mise/pull/14595) ([@&#8203;jdx](https://redirect.github.com/jdx))
- On fish, mise no longer recurses until fish hits its call stack limit after `mise implode`. [#&#8203;14594](https://redirect.github.com/jdx/mise/pull/14594) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;thernstig](https://redirect.github.com/thernstig))
- `mise bootstrap` now finds Nix in `/nix/var/nix/profiles/default/bin` when a `pre-packages` hook installed it earlier in the same run, so `nix:` packages install. [#&#8203;14596](https://redirect.github.com/jdx/mise/pull/14596) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `MISE_WATCH_FILES_GIT_REWRITTEN` is no longer set when HEAD stayed on the same commit, for example after `git checkout -b`, `git reset` or `git branch -m`. Before, format-on-edit hooks skipped files you had just edited. [#&#8203;14585](https://redirect.github.com/jdx/mise/pull/14585) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- Command wrapper templates can now use `config_root` and `config_source`. Relative paths in template functions resolve from the wrapper's config file instead of the current directory. [#&#8203;14628](https://redirect.github.com/jdx/mise/pull/14628) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

#### Upgrading

- **Unsupported tool options:** if an install now fails with `option '...' is not used by the ... backend`, remove the option or fix its spelling. `mise config explain tools` shows which keys mise doesn't recognize.
- **Paranoid mode:** after upgrading, run `mise trust` in each project to approve its env files. Run it again after editing them. If you only want the env-file check, set `paranoid = ["env_files"]`.
- **Firewall config on macOS and Windows:** if a shared config has an invalid `[bootstrap.linux.firewall]` value, it now fails to load on macOS and Windows too. Correct the value.

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.13...v2026.10.14>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.13`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.13): : Untrusted project configs can no longer change settings

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.12...vfox-v2026.10.13)

mise now ignores `[settings]` in a project config until you trust it. This release also adds per-version-range backend options to the registry and template support for command wrappers and `[bootstrap.repos]`. It fixes several bugs in lockfiles, tasks and multi-version tool options.

#### Security

- **`[settings]` from untrusted project configs is ignored.** Before, a cloned repo's `mise.toml` could change settings as soon as mise found it, with no trust prompt. That included settings that redirect downloads or feed build commands, such as `node.mirror_url`, `python.pyenv_repo` and `ruby.precompiled_url`. These settings now apply only after `mise trust`, like `[env]`, `[hooks]` and `[tools]` options already did. Trusted projects, global and system config, environment variables and CLI flags work as before. Commands that trust the active config implicitly (`mise run`, `mise exec`, `mise install`) reload settings after trusting it, so the project's settings still apply to that same command. Settings files are now parsed once per load. [#&#8203;14538](https://redirect.github.com/jdx/mise/pull/14538), [#&#8203;14553](https://redirect.github.com/jdx/mise/pull/14553) ([@&#8203;jdx](https://redirect.github.com/jdx))

  For example, if an untrusted repo sets `[settings.python] pyenv_repo` to its own URL, `mise settings get python.pyenv_repo` returns the default pyenv repository until you run `mise trust`.

- mise's own `zip` dependency moved to 9.x, which fixes GHSA-4684-h2q9-2vcp. Older versions applied Unix permissions through extracted symlinks without checking that the target stayed inside the destination. mise uses this code to extract downloaded `.zip` tool archives. [#&#8203;14577](https://redirect.github.com/jdx/mise/pull/14577) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Added

- **One registry backend can carry different options for each version range.** A registry entry can list the same backend more than once, each time with its own `min_version`/`max_version` and `options`. Tools whose release assets changed at some version can then stay on one backend. Two entries can't give the same version different options unless their `platforms` don't overlap. Table syntax such as `tool = { version = "1.4.0" }` now takes its registry defaults from the entry that version selects. Older mise releases apply only the first entry's options to every version. [#&#8203;14543](https://redirect.github.com/jdx/mise/pull/14543) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  backends = [
    { full = "github:example/tool", min_version = "2.0.0", options = { asset_pattern = "tool-v2-*" } },
    { full = "github:example/tool", max_version = "2.0.0", options = { asset_pattern = "tool-*" } },
  ]
  ```

- **`mise ls-remote` lists every version range of a registry tool.** When a tool's backends serve different version ranges, mise now lists each range from the backend that serves it. Before, it listed only the first backend's range. For example, `mise ls-remote hk` now starts at 0.1.4 instead of 1.58.1. Explicit backends, overrides, `--all` and `--strict-metadata` behave as before. [#&#8203;14544](https://redirect.github.com/jdx/mise/pull/14544) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **Command wrapper values render as templates.** `command`, `args` and `env` in `[wrappers]` can use `vars`, `env` and the usual template functions. `tools` and `config_root` are not available. A command that renders blank is an error. Use `{% raw %}` to keep literal braces, such as Docker's `{{.Names}}`. [#&#8203;14565](https://redirect.github.com/jdx/mise/pull/14565) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [wrappers.foo]
  command = "foo-real"
  args = ["--mode={{ vars.mode }}"]
  env = { FOO_HOME = "{{ [xdg_data_home, 'foo'] | join_path }}" }
  ```

- **`[bootstrap.repos]` supports templated `url`/`ref` and clone options.** `url` and `ref` render from `[vars]` or the process environment. Values from a config's `[env]` table are not available. `filter`, `depth` and `submodules = true` are passed to the first `git clone` as `--filter`, `--depth` and `--recurse-submodules`. Existing checkouts are left as they are. `depth` can't be combined with a commit SHA or a `refs/…` ref. [#&#8203;14548](https://redirect.github.com/jdx/mise/pull/14548) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;ovidiubuligan](https://redirect.github.com/ovidiubuligan))

  ```toml
  [bootstrap.repos]
  "~/src/large" = { url = "[email protected]:example/large.git", ref = "{{ vars.branch }}", filter = "blob:none", depth = 1 }
  ```

- **`{{ system_brew_prefix }}` template variable.** It resolves to the Homebrew prefix that `mise bootstrap` installs into: `/opt/homebrew` on macOS and `/home/linuxbrew/.linuxbrew` on Linux, or the value of `MISE_SYSTEM_BREW_PREFIX`. It does not detect a Homebrew install somewhere else. `[bootstrap.user].login_shell` now renders templates too, so `login_shell = "{{ system_brew_prefix }}/bin/zsh"` works. [#&#8203;14572](https://redirect.github.com/jdx/mise/pull/14572), [#&#8203;14570](https://redirect.github.com/jdx/mise/pull/14570) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **vfox `PreInstall` hooks can return a `headers` table** next to `url` and `sha256`. Plugins can now download private or token-gated artifacts through the normal install path, which handles checksums, retries and lockfile URLs. Headers are sent only to the origin of `url`, are dropped on cross-origin redirects, are redacted in debug output and are never written to `mise.lock`. An explicit `Authorization` header overrides netrc and the automatic GitHub token. [#&#8203;14531](https://redirect.github.com/jdx/mise/pull/14531) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`mise doctor` reports a stale `mise-shim.exe` on Windows.** In `windows_shim_mode = "exe"`, shims are copies of the `mise-shim.exe` that sits beside `mise.exe`. If that template is older than mise, or has no version tag (every template built before this release), `mise doctor` reports it and `mise reshim` warns. To fix it, update mise with the package manager that installed it, or run `mise self-update`, and then run `mise reshim --force`. [#&#8203;14586](https://redirect.github.com/jdx/mise/pull/14586) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **Lockfiles**
  - Running `mise lock` on Windows now records `ruby` entries for Windows, Linux and macOS platforms. Before, it skipped them, and `mise install --locked` then failed. [#&#8203;14520](https://redirect.github.com/jdx/mise/pull/14520), [#&#8203;14529](https://redirect.github.com/jdx/mise/pull/14529) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - Commands that rewrite `mise.lock` without locking again, such as `mise link`, no longer delete other platforms' option variants (for example, per-platform `asset_pattern` entries). [#&#8203;14519](https://redirect.github.com/jdx/mise/pull/14519) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - A `mise link` for one version of a tool no longer makes `mise install --locked` fail for that tool's other versions. A link now bypasses the lockfile only when the requested version could select it. [#&#8203;14539](https://redirect.github.com/jdx/mise/pull/14539) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - `mise install --locked` for Java no longer fetches the mise-java catalog when `mise.lock` already has the download URL, so locked installs work in air-gapped or proxied environments. [#&#8203;14534](https://redirect.github.com/jdx/mise/pull/14534) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **Tasks and shims**
  - `mise run` no longer hangs when parallel tasks need to install the same tool. [#&#8203;14536](https://redirect.github.com/jdx/mise/pull/14536) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - Running `mise install` inside a task no longer removes the shims for that task's `lazy` tools. [#&#8203;14533](https://redirect.github.com/jdx/mise/pull/14533) ([@&#8203;jdx](https://redirect.github.com/jdx))
  - Task hooks (such as `postinstall = { task = "setup" }`) and `watch_files` task hooks now run with mise even when a tool shim triggered them. Before, they could fail with errors like `jq: Unknown option --cd`. [#&#8203;14550](https://redirect.github.com/jdx/mise/pull/14550) ([@&#8203;jdx](https://redirect.github.com/jdx), [@&#8203;alitas](https://redirect.github.com/alitas))
  - `mise watch` exits quietly with status 130 when you interrupt watchexec with Ctrl+C, instead of printing an error. [#&#8203;14541](https://redirect.github.com/jdx/mise/pull/14541) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **Per-version tool options**
  - When a `github:`, `gitlab:` or `forgejo:` tool lists several versions in config, each version now installs with its own options. Before, an option such as `rename_exe` set only on the first entry applied to every version. [#&#8203;14537](https://redirect.github.com/jdx/mise/pull/14537) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
  - `vfox:` plugin hooks now receive only the options configured for the version they handle. [#&#8203;14562](https://redirect.github.com/jdx/mise/pull/14562) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- `mise install` now restores `cargo`, `rustc` and the host's `rust-std` when they are missing from a Rust toolchain with the `minimal` or `default` profile. Before, mise reported such a toolchain as installed. [#&#8203;14525](https://redirect.github.com/jdx/mise/pull/14525) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562), [@&#8203;risu729](https://redirect.github.com/risu729))
- The uv, pip and poetry deps providers now run again after mise recreates a deleted venv in a later process. Before, the new venv stayed empty. [#&#8203;14532](https://redirect.github.com/jdx/mise/pull/14532) ([@&#8203;halms](https://redirect.github.com/halms))
- vfox plugins now report an error, instead of crashing mise, when the HTTP client can't be built (for example, when no CA certificates are installed). [#&#8203;14576](https://redirect.github.com/jdx/mise/pull/14576) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- MCP resource read errors now include the underlying cause, such as the untrusted-config message and its `mise trust` hint. [#&#8203;14566](https://redirect.github.com/jdx/mise/pull/14566) ([@&#8203;diogochaves](https://redirect.github.com/diogochaves))

#### Registry

- `xcsift` 1.7.0 and later installs from signed Packslip releases and now supports Linux x64 as well as macOS ARM64. [#&#8203;14540](https://redirect.github.com/jdx/mise/pull/14540) ([@&#8203;jdx](https://redirect.github.com/jdx))
- `protoc-gen-connect-go` 2.x now installs from the `/v2` Go module path, and 1.x versions keep working. [#&#8203;14545](https://redirect.github.com/jdx/mise/pull/14545) ([@&#8203;jdx](https://redirect.github.com/jdx))
- Added `rea` (`npm:rea-agents`). [#&#8203;14421](https://redirect.github.com/jdx/mise/pull/14421) ([@&#8203;phucisstupid](https://redirect.github.com/phucisstupid))

#### Documentation

- The docs now state that OpenTelemetry export and spinel installs require `experimental = true`. [#&#8203;14552](https://redirect.github.com/jdx/mise/pull/14552) ([@&#8203;ZainnQureshii](https://redirect.github.com/ZainnQureshii))

#### New Contributors

- [@&#8203;phucisstupid](https://redirect.github.com/phucisstupid) made their first contribution in [#&#8203;14421](https://redirect.github.com/jdx/mise/pull/14421)
- [@&#8203;diogochaves](https://redirect.github.com/diogochaves) made their first contribution in [#&#8203;14566](https://redirect.github.com/jdx/mise/pull/14566)
- [@&#8203;ZainnQureshii](https://redirect.github.com/ZainnQureshii) made their first contribution in [#&#8203;14552](https://redirect.github.com/jdx/mise/pull/14552)
- [@&#8203;fang2hou](https://redirect.github.com/fang2hou) made their first contribution in [#&#8203;14551](https://redirect.github.com/jdx/mise/pull/14551)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.12...v2026.10.13>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.12`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.12): : Per-tool Python runtimes for PyPI tools, Gradle workspace discovery, and env file find_up

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.11...vfox-v2026.10.12)

PyPI and pipx tools can now choose their own Python runtime, the workspace graph can read Gradle multi-module builds, and `env._.file` can search parent directories. When mise-versions is having an outage, GitHub lookups now retry on `api.github.com`, and `_.source` scripts no longer pick up `~/.bashrc` when mise is launched from an editor.

#### Added

- **Per-tool Python for `pypi:` tools.** Set `python` to a minor series or an exact version, and mise installs that runtime and passes it to uv or pipx for that tool only. The runtime stays private to the tool and does not become your shell's default Python. `mise lock` records the selected Python with the tool, and `mise prune` keeps any runtime that a retained tool environment still uses. You can't combine `python` with `--python`/`-p` in `uvx_args` or `pipx_args`. Tools that don't set `python` behave as before. [#&#8203;14479](https://redirect.github.com/jdx/mise/pull/14479) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools]
  "pypi:black" = { version = "latest", python = "3.12" }
  "pypi:ruff" = { version = "latest", python = "3.12.12" }
  ```

  If you change a tool's `python`, mise installs a new environment instead of reusing the old one. If that install fails, the previous working environment is left untouched. [#&#8203;14504](https://redirect.github.com/jdx/mise/pull/14504) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **Gradle workspace discovery.** `mise tasks graph` and `mise run --affected` now find projects listed by `include` in `settings.gradle` or `settings.gradle.kts`, using Groovy or Kotlin syntax. Project IDs follow Gradle paths: `:app` becomes `gradle::app`, and the root project is `gradle::`. Literal `project(":lib")` references in a project's top-level `dependencies {}` block become graph edges. mise reads the scripts as text and never runs Gradle. If an `include` is computed or conditional, a `projectDir` isn't a plain `file("...")` literal, or both settings files exist, mise reports an error that points to `[monorepo.projects."gradle:..."]` overrides. Editing the settings script marks every Gradle project as affected. Based on a prototype by [@&#8203;Jarvvski](https://redirect.github.com/Jarvvski). [#&#8203;14512](https://redirect.github.com/jdx/mise/pull/14512) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`find_up` for `env._.file`.** With `find_up = true`, mise looks for a relative path in the current directory and in every parent, so a global config can load each project's `.env` files. Files load from the root down, so values in closer files win. The default is `false`, and the option has no effect on absolute paths. [#&#8203;14497](https://redirect.github.com/jdx/mise/pull/14497) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  # ~/.config/mise/config.toml
  [env]
  _.file = [
    { path = ".env", find_up = true },
    { path = ".env.local", find_up = true },
  ]
  ```

- **Detect git-rewritten files in `watch_files` hooks.** mise sets `MISE_WATCH_FILES_GIT_REWRITTEN=1` when every changed file was last written before git's most recent HEAD move (checkout, pull, rebase). Commits don't count as HEAD moves. Hooks such as format-on-edit can use this to skip files the user didn't edit. `git stash pop`/`apply` don't move HEAD, so mise doesn't detect them. [#&#8203;14511](https://redirect.github.com/jdx/mise/pull/14511) ([@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [[watch_files]]
  patterns = ["**/*.go"]
  run = '[ -n "$MISE_WATCH_FILES_GIT_REWRITTEN" ] || { IFS=:; hk fix $MISE_WATCH_FILES_MODIFIED; }'
  ```

- **`.tera` sources inferred for dotfile group templates.** A `mode = "template"` entry in `[dotfile_groups]` no longer needs a `source` just to point at a `.tera` file. If the exact path doesn't exist, mise falls back to the same path with `.tera` added. An explicit `source` still takes precedence, and the `.tera` file is not also deployed under its suffixed name. [#&#8203;14502](https://redirect.github.com/jdx/mise/pull/14502) ([@&#8203;jdx](https://redirect.github.com/jdx))

- **`mise lock` warns about unsupported platforms.** If you name platforms with `lockfile_platforms` or `mise lock --platform`, mise prints a warning for each tool version that has no build for one of them, for example `[email protected].0 is not available for windows-x64; it will not install there`. The default platform set still only logs this at debug level, and the exit status is unchanged. [#&#8203;14500](https://redirect.github.com/jdx/mise/pull/14500) ([@&#8203;jdx](https://redirect.github.com/jdx))

- Registry: added `martin` (`github:maplibre/martin`). [#&#8203;14488](https://redirect.github.com/jdx/mise/pull/14488) ([@&#8203;ivan-ivkovic](https://redirect.github.com/ivan-ivkovic), [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **GitHub lookups survive mise-versions outages.** If mise-versions times out, drops the connection, or returns `429` or `5xx`, mise now retries the lookup on `api.github.com`, using your GitHub token if one is set. This covers releases, tags, repository contents, attestations and `self-update`. A `404`, `403` or empty answer from mise-versions is still trusted and not retried, so normal use doesn't spend GitHub's anonymous rate limit. If both sources fail, the error shows both failures, and a previously cached version list is still used. [#&#8203;14501](https://redirect.github.com/jdx/mise/pull/14501) ([@&#8203;jdx](https://redirect.github.com/jdx))
- **`_.source` no longer loads `~/.bashrc`.** When mise's stdin was a socket and `SHLVL` was unset, which happens when an editor extension such as the VS Code mise extension starts it, bash sourced `~/.bashrc` before the script. That leaked variables into the environment and could drop the script's `PATH` entries. The script now runs with `--norc`. [#&#8203;14487](https://redirect.github.com/jdx/mise/pull/14487) ([@&#8203;pikeas](https://redirect.github.com/pikeas))
- **Each version entry uses its own options.** When a tool listed several versions, options set only on the first entry leaked into the later ones. For example, `prerelease = true` on a `latest` entry made a separate `"1"` entry resolve to a prerelease. [#&#8203;14476](https://redirect.github.com/jdx/mise/pull/14476) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562), [@&#8203;risu729](https://redirect.github.com/risu729))
- **Project overrides now work when a workspace provider fails.** Previously, if a provider couldn't read its manifest (for example, a malformed `go.work`), `mise tasks graph` and `mise run --affected` failed for the whole repo. Declaring a project in that provider's namespace under `[monorepo.projects]` now replaces the provider's output and clears its error. Providers with no declared projects still fail loudly. [#&#8203;14508](https://redirect.github.com/jdx/mise/pull/14508) ([@&#8203;jdx](https://redirect.github.com/jdx))

#### Documentation

- Explained why `mise activate` keeps shims on `PATH` [#&#8203;14493](https://redirect.github.com/jdx/mise/pull/14493) ([@&#8203;jdx](https://redirect.github.com/jdx)), and clarified the platform-specific mise cache paths for CI [#&#8203;14506](https://redirect.github.com/jdx/mise/pull/14506) ([@&#8203;zeitlinger](https://redirect.github.com/zeitlinger)).

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.11...v2026.10.12>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.11`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.11): : Much faster installs and far fewer GitHub API calls

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.10...vfox-v2026.10.11)

mise is much faster in this release and calls the GitHub API far less. Version lists, release tags, attestations and other public GitHub metadata now come from mise-versions, so everyday commands like `mise install`, `exec`, shims, `outdated`, `lock` and `self-update` are quicker and users without a GitHub token no longer run into the 60-requests-an-hour limit. Cached version lists are reused for longer, so repeat installs skip most lookups. It also adds `mise bootstrap --upgrade` and `mise env --paths`, and closes a trust gap in remote task files.

#### Speed and GitHub API usage

Cold-cache timings for v2026.10.10 versus v2026.10.11, measured with hyperfine on one Linux x64 machine with no GitHub token set:

| Command                                               | v2026.10.10 | v2026.10.11 |
| ----------------------------------------------------- | ----------- | ----------- |
| `mise ls-remote python`                               | 961 ms      | 93 ms       |
| `mise install` (jq, ripgrep, fd, bat, gh, shellcheck) | 1.49 s      | 1.08 s      |
| `mise outdated` (same six tools installed)            | 433 ms      | 272 ms      |

The install and `outdated` runs are noisy (±0.1–0.4s over 4 runs) and the install time includes downloads, so treat them as rough. Requests to `api.github.com` in a cold `mise install` of `github:` tools dropped from 2 to 0. Cold `mise outdated` made 8 network requests before and none now.

- **Public GitHub metadata comes from mise-versions.** With `use_versions_host` on (the default), release lists, `latest`, git tags (aqua `github_tag` packages, ubi and go), artifact attestations (including aqua's `predicate_type` filters) and the digest in the checksum-mismatch hint are read from mise-versions instead of `api.github.com`. GitHub is still asked directly for repositories mise-versions doesn't list when you have a token (the private-repository case), and in paranoid mode for attestations. The direct API is also used with `use_versions_host = false`, GitHub `url_replacements` and custom `api_url`s. [#&#8203;14460](https://redirect.github.com/jdx/mise/pull/14460), [#&#8203;14461](https://redirect.github.com/jdx/mise/pull/14461), [#&#8203;14469](https://redirect.github.com/jdx/mise/pull/14469), [#&#8203;14481](https://redirect.github.com/jdx/mise/pull/14481), [#&#8203;14477](https://redirect.github.com/jdx/mise/pull/14477), [#&#8203;14443](https://redirect.github.com/jdx/mise/pull/14443)
- Other requests also moved off the GitHub API:
  - `mise x`, `mise exec`, shims and `hook-env` use mise-versions when they auto-install a tool. [#&#8203;14458](https://redirect.github.com/jdx/mise/pull/14458)
  - `mise self-update` reads mise's releases from mise-versions and downloads the archive from github.com release downloads. Packslip and zipsign verification are unchanged. [#&#8203;14464](https://redirect.github.com/jdx/mise/pull/14464)
  - `mise lock` downloads artifacts for provenance checks from github.com instead of the API asset endpoint. [#&#8203;14465](https://redirect.github.com/jdx/mise/pull/14465)
  - Aqua registries hosted on GitHub are fetched from raw\.githubusercontent.com. [#&#8203;14470](https://redirect.github.com/jdx/mise/pull/14470)
  - Ruby looks up the latest ruby-build release through mise-versions. [#&#8203;14471](https://redirect.github.com/jdx/mise/pull/14471)
- **Fewer version-list fetches.** `mise install`, `use` and `run` now reuse a cached version list for the tool's `minimum_release_age` window minus one hour, clamped to 1–24h. With the default 24h cutoff, that is 23h. `outdated`, `upgrade`, `ls-remote`, `lock` and `latest` still refresh after 1h. If you set `fetch_remote_versions_cache` yourself, your value applies to every command. [#&#8203;14484](https://redirect.github.com/jdx/mise/pull/14484), [#&#8203;14485](https://redirect.github.com/jdx/mise/pull/14485), [#&#8203;14457](https://redirect.github.com/jdx/mise/pull/14457)
- **Faster version resolution:**
  - Multi-tool commands such as `mise outdated` can resolve many registry tools with one request to the mise-versions index. [#&#8203;14473](https://redirect.github.com/jdx/mise/pull/14473)
  - `latest` is resolved without refetching the list. [#&#8203;14456](https://redirect.github.com/jdx/mise/pull/14456)
  - Explicit backend args such as `aqua:jqlang/jq` reuse the registry tool's version list. [#&#8203;14459](https://redirect.github.com/jdx/mise/pull/14459)
  - Release tags come from the versions host instead of being listed or guessed. [#&#8203;14442](https://redirect.github.com/jdx/mise/pull/14442)
  - Packslip installs no longer re-download what resolution already verified. [#&#8203;14466](https://redirect.github.com/jdx/mise/pull/14466)
  - Install tracking and the Python version list are served from static assets. [#&#8203;14441](https://redirect.github.com/jdx/mise/pull/14441)
- **Python version listing no longer clones pyenv.** `mise ls-remote python`, `outdated`, `upgrade` and fuzzy `mise use [email protected]` read the python-build definitions from mise-versions. A cold listing took about 960ms before and about 90ms now. pyenv is still cloned when mise compiles Python, when `python.pyenv_repo` points at a fork, when you're offline, or when `use_versions_host = false`. Some definitions that had no date before now have one, so they are held back by `minimum_release_age` until they are old enough. [#&#8203;14455](https://redirect.github.com/jdx/mise/pull/14455)
- `mise oci build` now requires root for `apt:` packages, as it already did for apk, and fails up front with a clear message. If apt-get still fails, the error now includes the last lines of dpkg's output. [#&#8203;14447](https://redirect.github.com/jdx/mise/pull/14447)
- Registry: `native-sdk` now installs from npm (`@native-sdk/cli`), so the `native` command works. It requires Node 24+. [#&#8203;14431](https://redirect.github.com/jdx/mise/pull/14431)

#### Security

- **Remote task files now need trust before their headers render.** A `mise.toml` with only `[tasks]` loads without trust. Before this fix, if one of those tasks set `file` to a remote URL, `mise tasks ls`, `mise tasks info` and `mise tasks validate` rendered the file's `#MISE` headers, so a template like `{{ exec(...) }}` ran without a trust prompt. Remote files now follow the same rule as local task includes: if the headers contain template syntax, the config that references them must be trusted first. Remote files with plain headers still list without trust, and braces in the script body need no trust. [#&#8203;14416](https://redirect.github.com/jdx/mise/pull/14416) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562), [@&#8203;risu729](https://redirect.github.com/risu729))
- Updated rustls to 0.23.46 to address a RustSec advisory. [#&#8203;14434](https://redirect.github.com/jdx/mise/pull/14434)

#### Added

- **`mise bootstrap --upgrade`** installs what is missing and then upgrades what is already installed, in a single run. In the packages phase it runs the same upgrade as `mise bootstrap packages upgrade`, and pins are respected. In the tools phase it runs `mise upgrade` without `--bump`, so configured version ranges hold. Both run before the matching post hooks and dependent services. `--dry-run` previews both steps and `--skip` works as before. It does not update mise itself, repositories or images, and it does not restart services. [#&#8203;14433](https://redirect.github.com/jdx/mise/pull/14433)

  ```sh
  mise self-update --yes && mise bootstrap --upgrade
  ```

- **`mise env --paths`** prints the PATH entries mise manages, one per line and in PATH order: tool bin directories, `_.path` entries, and inherited directories that mise moves forward, such as `~/.cargo/bin`. This lets CI integrations export only mise's additions without diffing `PATH`. [#&#8203;14429](https://redirect.github.com/jdx/mise/pull/14429)

  ```console
  $ mise env --paths
  /home/me/.local/share/mise/installs/node/24.11.0/bin
  /home/me/.cargo/bin
  ```

- **The install script downloads pinned versions from mise.jdx.dev.** `curl https://mise.run | MISE_VERSION=<version> sh` now tries mise.jdx.dev first and falls back to GitHub Releases if that download fails. Checksums for pinned versions still come only from GitHub's `SHASUMS256.txt`. If no checksum is found, the script refuses to install. `MISE_INSTALL_FROM_GITHUB=1` still uses GitHub only. [#&#8203;14446](https://redirect.github.com/jdx/mise/pull/14446)

- Registry: `gdal` installs the GDAL/OGR command-line tools from conda-forge (`conda:libgdal-core`). [#&#8203;14440](https://redirect.github.com/jdx/mise/pull/14440) ([@&#8203;ivan-ivkovic](https://redirect.github.com/ivan-ivkovic))

#### Fixed

- **A bare `mise upgrade` no longer installs lazy tools that have never been installed.** Naming the tool still installs it. A lazy tool that already has a version installed upgrades as before. A missing lazy tool is still installed if an upgraded tool depends on it. `mise outdated` now marks these tools as `[MISSING] (lazy)`, and the bare upgrade no longer looks up their versions. [#&#8203;14422](https://redirect.github.com/jdx/mise/pull/14422) ([@&#8203;ryanrhughes](https://redirect.github.com/ryanrhughes)), [#&#8203;14444](https://redirect.github.com/jdx/mise/pull/14444)
- Fixed false `stack limit reached` errors on the static musl builds, for example on Ubuntu 26.04. These errors broke aqua `version_filter` parsing and made shims such as kubectl's report "not a valid shim". [#&#8203;14450](https://redirect.github.com/jdx/mise/pull/14450)
- `mise lock --platform ...` run on Alpine or NixOS no longer records Node source builds for other platforms. Only an explicit `node.compile` or `all_compile = true` forces source builds for those targets. [#&#8203;14432](https://redirect.github.com/jdx/mise/pull/14432)
- `mise upgrade --bump` and hand-edited version pins no longer drop a pipx tool's locked uv dependency graph. Before, `mise install --locked` then failed on a fresh checkout. [#&#8203;14426](https://redirect.github.com/jdx/mise/pull/14426) ([@&#8203;wislertt](https://redirect.github.com/wislertt))
- Packslip prefixes such as `fnox@1` now skip releases this host can't install, the same way `latest` does. Each skipped release gets a warning, and if no match can

> [!IMPORTANT]
> ✂ PR body was truncated to here.


</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:28:33 -07:00
renovate[bot] 88547952df chore(deps): update node.js to v24.22.0 (#726)
> [!NOTE]
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [node](https://nodejs.org) ([source](https://redirect.github.com/nodejs/node)) | tools | minor | `24.21.0` → `24.22.0` |

---

### Release Notes

<details>
<summary>nodejs/node (node)</summary>

### [`v24.22.0`](https://redirect.github.com/nodejs/node/releases/tag/v24.22.0): 2026-10-14, Version 24.22.0 'Krypton' (LTS), @&#8203;richardlau prepared by @&#8203;aduh95

[Compare Source](https://redirect.github.com/nodejs/node/compare/v24.21.0...v24.22.0)

##### Notable Changes

- \[[`ddbf974b47`](https://redirect.github.com/nodejs/node/commit/ddbf974b47)] - **(SEMVER-MINOR)** **crypto**: add `crypto.parsePKCS12()` (Brian Muenzenmeyer) [#&#8203;65627](https://redirect.github.com/nodejs/node/pull/65627)
- \[[`c162c68ff0`](https://redirect.github.com/nodejs/node/commit/c162c68ff0)] - **(SEMVER-MINOR)** **crypto**: add a generic MAC API (Filip Skokan) [#&#8203;65553](https://redirect.github.com/nodejs/node/pull/65553)
- \[[`a4ab3d84d7`](https://redirect.github.com/nodejs/node/commit/a4ab3d84d7)] - **(SEMVER-MINOR)** **crypto**: discover ciphers from OpenSSL providers (Filip Skokan) [#&#8203;65484](https://redirect.github.com/nodejs/node/pull/65484)
- \[[`c2f1e829ea`](https://redirect.github.com/nodejs/node/commit/c2f1e829ea)] - **(SEMVER-MINOR)** **crypto**: discover hashes from OpenSSL providers (Filip Skokan) [#&#8203;65484](https://redirect.github.com/nodejs/node/pull/65484)
- \[[`51aad5a80d`](https://redirect.github.com/nodejs/node/commit/51aad5a80d)] - **(SEMVER-MINOR)** **crypto**: enable SIV and GCM-SIV modes in Cipher/Decipher APIs (Filip Skokan) [#&#8203;63411](https://redirect.github.com/nodejs/node/pull/63411)
- \[[`d11c65d2f2`](https://redirect.github.com/nodejs/node/commit/d11c65d2f2)] - **doc**: add araujogui to collaborators (Guilherme Araújo) [#&#8203;66090](https://redirect.github.com/nodejs/node/pull/66090)
- \[[`acc1578179`](https://redirect.github.com/nodejs/node/commit/acc1578179)] - **doc**: deprecate `Server.prototype._listen2` in `node:net` (Antoine du Hamel) [#&#8203;65593](https://redirect.github.com/nodejs/node/pull/65593)
- \[[`a7f8de6362`](https://redirect.github.com/nodejs/node/commit/a7f8de6362)] - **(SEMVER-MINOR)** **net**: support sending `net.BoundSocket` to threads and child processes (Guy Bedford) [#&#8203;64725](https://redirect.github.com/nodejs/node/pull/64725)
- \[[`60c3eb61bd`](https://redirect.github.com/nodejs/node/commit/60c3eb61bd)] - **(SEMVER-MINOR)** **perf_hooks**: implement `SlidingWindowHistogram` (James M Snell) [#&#8203;65825](https://redirect.github.com/nodejs/node/pull/65825)
- \[[`6b3e6f9d5c`](https://redirect.github.com/nodejs/node/commit/6b3e6f9d5c)] - **(SEMVER-MINOR)** **perf_hooks**: implement qrde analysis support in Histogram (James M Snell) [#&#8203;65806](https://redirect.github.com/nodejs/node/pull/65806)
- \[[`75456a3512`](https://redirect.github.com/nodejs/node/commit/75456a3512)] - **(SEMVER-MINOR)** **perf_hooks**: implement Histogram `meanCI` API (James M Snell) [#&#8203;65606](https://redirect.github.com/nodejs/node/pull/65606)
- \[[`72d1b40c56`](https://redirect.github.com/nodejs/node/commit/72d1b40c56)] - **(SEMVER-MINOR)** **perf_hooks**: add CBOR export/import for histogram exchange (James M Snell) [#&#8203;65434](https://redirect.github.com/nodejs/node/pull/65434)
- \[[`41161dda87`](https://redirect.github.com/nodejs/node/commit/41161dda87)] - **(SEMVER-MINOR)** **sqlite**: add `StatementSync.prototype.close()` (Guilherme Araújo) [#&#8203;64232](https://redirect.github.com/nodejs/node/pull/64232)
- \[[`adbc757d70`](https://redirect.github.com/nodejs/node/commit/adbc757d70)] - **(SEMVER-MINOR)** **sqlite**: add `StatementSync.prototype[Symbol.dispose]()` (Guilherme Araújo) [#&#8203;64232](https://redirect.github.com/nodejs/node/pull/64232)
- \[[`241339f072`](https://redirect.github.com/nodejs/node/commit/241339f072)] - **(SEMVER-MINOR)** **src,lib**: add `util.markPromiseAsHandled` (James M Snell) [#&#8203;65805](https://redirect.github.com/nodejs/node/pull/65805)
- \[[`63b0974d9e`](https://redirect.github.com/nodejs/node/commit/63b0974d9e)] - **(SEMVER-MINOR)** **util**: implement `util.throttle` (James M Snell) [#&#8203;65899](https://redirect.github.com/nodejs/node/pull/65899)
- \[[`a3bbb5a26f`](https://redirect.github.com/nodejs/node/commit/a3bbb5a26f)] - **(SEMVER-MINOR)** **util**: implement `debounce` (James M Snell) [#&#8203;65899](https://redirect.github.com/nodejs/node/pull/65899)

##### Commits

- \[[`e6832b2cb1`](https://redirect.github.com/nodejs/node/commit/e6832b2cb1)] - **assert**: fix TypeError on deepStrictEqual with null Map key or Set member (Sergey Sannikov) [#&#8203;64449](https://redirect.github.com/nodejs/node/pull/64449)
- \[[`a079177d9f`](https://redirect.github.com/nodejs/node/commit/a079177d9f)] - **benchmark**: add --csv option to compare.js with --analyze (James M Snell) [#&#8203;65922](https://redirect.github.com/nodejs/node/pull/65922)
- \[[`10b1a0f70f`](https://redirect.github.com/nodejs/node/commit/10b1a0f70f)] - **benchmark**: fix max-regressions detection in compare.js (James M Snell) [#&#8203;65587](https://redirect.github.com/nodejs/node/pull/65587)
- \[[`e0c7ba8ad5`](https://redirect.github.com/nodejs/node/commit/e0c7ba8ad5)] - **benchmark**: add --analyze option to benchmark/scatter.js (James M Snell) [#&#8203;65594](https://redirect.github.com/nodejs/node/pull/65594)
- \[[`21fed10bb0`](https://redirect.github.com/nodejs/node/commit/21fed10bb0)] - **benchmark**: add crypto class benchmarks (Filip Skokan) [#&#8203;65518](https://redirect.github.com/nodejs/node/pull/65518)
- \[[`fa912242b7`](https://redirect.github.com/nodejs/node/commit/fa912242b7)] - **buffer**: fix unaligned UTF-16LE decoding (Matteo Collina) [#&#8203;65905](https://redirect.github.com/nodejs/node/pull/65905)
- \[[`a6bd0c1413`](https://redirect.github.com/nodejs/node/commit/a6bd0c1413)] - **build**: suppress OpenSSL asm warnings with clang (Richard Lau) [#&#8203;66023](https://redirect.github.com/nodejs/node/pull/66023)
- \[[`94e12139ee`](https://redirect.github.com/nodejs/node/commit/94e12139ee)] - **build**: derive V8_LOGGING_LEVEL from dcheck_always_on (Joyee Cheung) [#&#8203;65744](https://redirect.github.com/nodejs/node/pull/65744)
- \[[`7a2f322943`](https://redirect.github.com/nodejs/node/commit/7a2f322943)] - **build**: fix quiet default for make builds (Shelley Vohr) [#&#8203;65826](https://redirect.github.com/nodejs/node/pull/65826)
- \[[`c280d650b0`](https://redirect.github.com/nodejs/node/commit/c280d650b0)] - **build**: allow linking shared dependencies in the GN build (Shelley Vohr) [#&#8203;65797](https://redirect.github.com/nodejs/node/pull/65797)
- \[[`4b23a4472c`](https://redirect.github.com/nodejs/node/commit/4b23a4472c)] - **build**: enable the V8 sandbox in shared-cage builds (Shelley Vohr) [#&#8203;62237](https://redirect.github.com/nodejs/node/pull/62237)
- \[[`676e15c003`](https://redirect.github.com/nodejs/node/commit/676e15c003)] - **build**: define V8_CONTIGUOUS_COMPRESSED_RO_SPACE for shared cage (Shelley Vohr) [#&#8203;65464](https://redirect.github.com/nodejs/node/pull/65464)
- \[[`f784058482`](https://redirect.github.com/nodejs/node/commit/f784058482)] - **build**: remove obsolete configure flags (Chengzhong Wu) [#&#8203;65456](https://redirect.github.com/nodejs/node/pull/65456)
- \[[`f63f37d243`](https://redirect.github.com/nodejs/node/commit/f63f37d243)] - **build**: activate correct default flags for riscv64 (Stewart X Addison) [#&#8203;65708](https://redirect.github.com/nodejs/node/pull/65708)
- \[[`da46ac075d`](https://redirect.github.com/nodejs/node/commit/da46ac075d)] - **build**: derive NODE_ARCH from target_cpu in the GN build (Shelley Vohr) [#&#8203;65491](https://redirect.github.com/nodejs/node/pull/65491)
- \[[`249e9a16dd`](https://redirect.github.com/nodejs/node/commit/249e9a16dd)] - **build,src**: gate jspi flag on V8_ENABLE_WEBASSEMBLY (Piotr Kubaj) [#&#8203;66082](https://redirect.github.com/nodejs/node/pull/66082)
- \[[`146f00f64b`](https://redirect.github.com/nodejs/node/commit/146f00f64b)] - **child_process**: clear timeout timer on spawn-time error too (kishore280) [#&#8203;65506](https://redirect.github.com/nodejs/node/pull/65506)
- \[[`13987f4418`](https://redirect.github.com/nodejs/node/commit/13987f4418)] - **crypto**: remove redundent `std::move` call (Antoine du Hamel) [#&#8203;66111](https://redirect.github.com/nodejs/node/pull/66111)
- \[[`ef957ac85f`](https://redirect.github.com/nodejs/node/commit/ef957ac85f)] - **crypto**: skip private RSA parameters in key details (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`8feccc5e22`](https://redirect.github.com/nodejs/node/commit/8feccc5e22)] - **crypto**: use provider EC group names (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`764e0d1120`](https://redirect.github.com/nodejs/node/commit/764e0d1120)] - **crypto**: fetch ciphers for private-key encoding (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`50ffed1979`](https://redirect.github.com/nodejs/node/commit/50ffed1979)] - **crypto**: decode [PKCS#1](https://redirect.github.com/PKCS/node/issues/1) keys through providers (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`56c16b2fd6`](https://redirect.github.com/nodejs/node/commit/56c16b2fd6)] - **crypto**: derive keys through EVP_KDF (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`a4effe6809`](https://redirect.github.com/nodejs/node/commit/a4effe6809)] - **crypto**: use names for asymmetric key algorithms (Filip Skokan) [#&#8203;65966](https://redirect.github.com/nodejs/node/pull/65966)
- \[[`ddbf974b47`](https://redirect.github.com/nodejs/node/commit/ddbf974b47)] - **(SEMVER-MINOR)** **crypto**: add crypto.parsePKCS12() (Brian Muenzenmeyer) [#&#8203;65627](https://redirect.github.com/nodejs/node/pull/65627)
- \[[`8e631aa48f`](https://redirect.github.com/nodejs/node/commit/8e631aa48f)] - **crypto**: add Hybrid KEMs to Web Cryptography (Filip Skokan) [#&#8203;65759](https://redirect.github.com/nodejs/node/pull/65759)
- \[[`5c51129e16`](https://redirect.github.com/nodejs/node/commit/5c51129e16)] - **crypto**: optimize private EC JWK import (Filip Skokan) [#&#8203;65908](https://redirect.github.com/nodejs/node/pull/65908)
- \[[`c2ec40c2d7`](https://redirect.github.com/nodejs/node/commit/c2ec40c2d7)] - **crypto**: disable non-FIPS WebCrypto paths in FIPS mode (Filip Skokan) [#&#8203;65172](https://redirect.github.com/nodejs/node/pull/65172)
- \[[`e198aa0f79`](https://redirect.github.com/nodejs/node/commit/e198aa0f79)] - **crypto**: read RSA-PSS restrictions from provider (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`253784f0e9`](https://redirect.github.com/nodejs/node/commit/253784f0e9)] - **crypto**: optimize and benchmark key preparation (Filip Skokan) [#&#8203;65892](https://redirect.github.com/nodejs/node/pull/65892)
- \[[`80a6ad509b`](https://redirect.github.com/nodejs/node/commit/80a6ad509b)] - **crypto**: avoid EC reconstruction for signature sizing (Filip Skokan) [#&#8203;65908](https://redirect.github.com/nodejs/node/pull/65908)
- \[[`5b592b772c`](https://redirect.github.com/nodejs/node/commit/5b592b772c)] - **crypto**: avoid EC raw export reconstruction (Filip Skokan) [#&#8203;65908](https://redirect.github.com/nodejs/node/pull/65908)
- \[[`ee956ae3bf`](https://redirect.github.com/nodejs/node/commit/ee956ae3bf)] - **crypto**: export EC JWK coordinates directly (Filip Skokan) [#&#8203;65908](https://redirect.github.com/nodejs/node/pull/65908)
- \[[`684008be58`](https://redirect.github.com/nodejs/node/commit/684008be58)] - **crypto**: read EC curve metadata directly (Filip Skokan) [#&#8203;65908](https://redirect.github.com/nodejs/node/pull/65908)
- \[[`f4665f1c41`](https://redirect.github.com/nodejs/node/commit/f4665f1c41)] - **crypto**: validate the limit of PBKDF2 iterations (Filip Skokan) [#&#8203;65704](https://redirect.github.com/nodejs/node/pull/65704)
- \[[`624abbecd7`](https://redirect.github.com/nodejs/node/commit/624abbecd7)] - **crypto**: use primordials in HKDF info validation (Filip Skokan) [#&#8203;65704](https://redirect.github.com/nodejs/node/pull/65704)
- \[[`e4d916f175`](https://redirect.github.com/nodejs/node/commit/e4d916f175)] - **crypto**: add mgf1Hash for RSA-OAEP (Adam Mcgrath) [#&#8203;65073](https://redirect.github.com/nodejs/node/pull/65073)
- \[[`db8a9162a8`](https://redirect.github.com/nodejs/node/commit/db8a9162a8)] - **crypto**: fix multi-prime RSA JWKs (Filip Skokan) [#&#8203;65649](https://redirect.github.com/nodejs/node/pull/65649)
- \[[`022ac83504`](https://redirect.github.com/nodejs/node/commit/022ac83504)] - **crypto**: cache valid ECDH key pairs (Filip Skokan) [#&#8203;65615](https://redirect.github.com/nodejs/node/pull/65615)
- \[[`c61953ebcc`](https://redirect.github.com/nodejs/node/commit/c61953ebcc)] - **crypto**: add strict mode to --force-fips (Filip Skokan) [#&#8203;65645](https://redirect.github.com/nodejs/node/pull/65645)
- \[[`db6ea132a5`](https://redirect.github.com/nodejs/node/commit/db6ea132a5)] - **crypto**: add FIPS indicator diagnostics channel (Filip Skokan) [#&#8203;65645](https://redirect.github.com/nodejs/node/pull/65645)
- \[[`c162c68ff0`](https://redirect.github.com/nodejs/node/commit/c162c68ff0)] - **(SEMVER-MINOR)** **crypto**: add a generic MAC API (Filip Skokan) [#&#8203;65553](https://redirect.github.com/nodejs/node/pull/65553)
- \[[`2b62aafd20`](https://redirect.github.com/nodejs/node/commit/2b62aafd20)] - **crypto**: validate JWK usages before key_ops (Filip Skokan) [#&#8203;65550](https://redirect.github.com/nodejs/node/pull/65550)
- \[[`ec7819db69`](https://redirect.github.com/nodejs/node/commit/ec7819db69)] - **crypto**: fix RSA-PSS oversized salt handling (Filip Skokan) [#&#8203;65550](https://redirect.github.com/nodejs/node/pull/65550)
- \[[`a4ab3d84d7`](https://redirect.github.com/nodejs/node/commit/a4ab3d84d7)] - **(SEMVER-MINOR)** **crypto**: discover ciphers from OpenSSL providers (Filip Skokan) [#&#8203;65484](https://redirect.github.com/nodejs/node/pull/65484)
- \[[`c2f1e829ea`](https://redirect.github.com/nodejs/node/commit/c2f1e829ea)] - **(SEMVER-MINOR)** **crypto**: discover hashes from OpenSSL providers (Filip Skokan) [#&#8203;65484](https://redirect.github.com/nodejs/node/pull/65484)
- \[[`51aad5a80d`](https://redirect.github.com/nodejs/node/commit/51aad5a80d)] - **(SEMVER-MINOR)** **crypto**: enable SIV and GCM-SIV modes in Cipher/Decipher APIs (Filip Skokan) [#&#8203;63411](https://redirect.github.com/nodejs/node/pull/63411)
- \[[`5a46a9acbb`](https://redirect.github.com/nodejs/node/commit/5a46a9acbb)] - **crypto**: improve SubtleCrypto.supports() accuracy (Filip Skokan) [#&#8203;65222](https://redirect.github.com/nodejs/node/pull/65222)
- \[[`191ebfb26c`](https://redirect.github.com/nodejs/node/commit/191ebfb26c)] - **crypto**: fix public PKCS8 export error (한국) [#&#8203;65609](https://redirect.github.com/nodejs/node/pull/65609)
- \[[`b6d7ed372b`](https://redirect.github.com/nodejs/node/commit/b6d7ed372b)] - **crypto**: fix private SPKI export error (Filip Skokan) [#&#8203;65550](https://redirect.github.com/nodejs/node/pull/65550)
- \[[`79509c25a4`](https://redirect.github.com/nodejs/node/commit/79509c25a4)] - **crypto**: correct CCM decryption FIPS error message (kyungrae) [#&#8203;65450](https://redirect.github.com/nodejs/node/pull/65450)
- \[[`9adc87bc16`](https://redirect.github.com/nodejs/node/commit/9adc87bc16)] - **crypto**: harden X509Certificate state (Filip Skokan) [#&#8203;65518](https://redirect.github.com/nodejs/node/pull/65518)
- \[[`c63aa331b4`](https://redirect.github.com/nodejs/node/commit/c63aa331b4)] - **crypto**: optimize key slot caching (Filip Skokan) [#&#8203;65518](https://redirect.github.com/nodejs/node/pull/65518)
- \[[`87daea43a7`](https://redirect.github.com/nodejs/node/commit/87daea43a7)] - **crypto**: add null checks for OPENSSL_INIT_new() (Nora Dossche) [#&#8203;63457](https://redirect.github.com/nodejs/node/pull/63457)
- \[[`98ca5e9358`](https://redirect.github.com/nodejs/node/commit/98ca5e9358)] - **crypto**: prevent Hmac.digest() from returning uninitialized memory (Matteo Collina) [#&#8203;65112](https://redirect.github.com/nodejs/node/pull/65112)
- \[[`0c090a28cd`](https://redirect.github.com/nodejs/node/commit/0c090a28cd)] - **crypto**: avoid throwing CryptoKey brand checks (Filip Skokan) [#&#8203;65503](https://redirect.github.com/nodejs/node/pull/65503)
- \[[`a13cc3f723`](https://redirect.github.com/nodejs/node/commit/a13cc3f723)] - **crypto**: avoid throwing KeyObject brand checks (Filip Skokan) [#&#8203;65503](https://redirect.github.com/nodejs/node/pull/65503)
- \[[`b8db0045ef`](https://redirect.github.com/nodejs/node/commit/b8db0045ef)] - **deps**: V8: cherry-pick [`ea9c016`](https://redirect.github.com/nodejs/node/commit/ea9c016f26ad) (Gundepalli Sravani) [#&#8203;66570](https://redirect.github.com/nodejs/node/pull/66570)
- \[[`ba09b53487`](https://redirect.github.com/nodejs/node/commit/ba09b53487)] - **deps**: V8: backport [`c355350`](https://redirect.github.com/nodejs/node/commit/c3553509f460) (Jakob Linke) [#&#8203;66089](https://redirect.github.com/nodejs/node/pull/66089)
- \[[`9ce8ef405b`](https://redirect.github.com/nodejs/node/commit/9ce8ef405b)] - **deps**: V8: backport [`8901c79`](https://redirect.github.com/nodejs/node/commit/8901c793e439) (Toon Verwaest) [#&#8203;66089](https://redirect.github.com/nodejs/node/pull/66089)
- \[[`fb0d0ca289`](https://redirect.github.com/nodejs/node/commit/fb0d0ca289)] - **deps**: V8: backport [`0b94a9f`](https://redirect.github.com/nodejs/node/commit/0b94a9fd23ba) (Ruan Gustavo) [#&#8203;65753](https://redirect.github.com/nodejs/node/pull/65753)
- \[[`2faca508c0`](https://redirect.github.com/nodejs/node/commit/2faca508c0)] - **deps**: update googletest to [`8eff9e3`](https://redirect.github.com/nodejs/node/commit/8eff9e336692fc95961e096564f1044c600b881d) (Node.js GitHub Bot) [#&#8203;66009](https://redirect.github.com/nodejs/node/pull/66009)
- \[[`4396c9bffe`](https://redirect.github.com/nodejs/node/commit/4396c9bffe)] - **deps**: update googletest to [`283c175`](https://redirect.github.com/nodejs/node/commit/283c17563fe7a1111cd7f581aa5d541e8baeff2f) (Node.js GitHub Bot) [#&#8203;65833](https://redirect.github.com/nodejs/node/pull/65833)
- \[[`8a5b12570c`](https://redirect.github.com/nodejs/node/commit/8a5b12570c)] - **deps**: update simdjson to 4.6.11 (Node.js GitHub Bot) [#&#8203;65834](https://redirect.github.com/nodejs/node/pull/65834)
- \[[`c47feb09e8`](https://redirect.github.com/nodejs/node/commit/c47feb09e8)] - **deps**: update zlib to 1.3.2.1-motley-285e94b (Node.js GitHub Bot) [#&#8203;65835](https://redirect.github.com/nodejs/node/pull/65835)
- \[[`9f0d905286`](https://redirect.github.com/nodejs/node/commit/9f0d905286)] - **deps**: upgrade npm to 11.19.1 (npm team) [#&#8203;65573](https://redirect.github.com/nodejs/node/pull/65573)
- \[[`373cd332d8`](https://redirect.github.com/nodejs/node/commit/373cd332d8)] - **deps**: update googletest to [`36ba75f`](https://redirect.github.com/nodejs/node/commit/36ba75f0ad5383a9759f17f3f72fd4661c72cb6d) (Node.js GitHub Bot) [#&#8203;65654](https://redirect.github.com/nodejs/node/pull/65654)
- \[[`aeed6132a7`](https://redirect.github.com/nodejs/node/commit/aeed6132a7)] - **deps**: update simdjson to 4.6.9 (Node.js GitHub Bot) [#&#8203;65655](https://redirect.github.com/nodejs/node/pull/65655)
- \[[`2583571342`](https://redirect.github.com/nodejs/node/commit/2583571342)] - **deps**: update zlib to 1.3.2.1-motley-5eb4d7e (Node.js GitHub Bot) [#&#8203;65494](https://redirect.github.com/nodejs/node/pull/65494)
- \[[`d11c65d2f2`](https://redirect.github.com/nodejs/node/commit/d11c65d2f2)] - **doc**: add araujogui to collaborators (Guilherme Araújo) [#&#8203;66090](https://redirect.github.com/nodejs/node/pull/66090)
- \[[`0a4a8d602a`](https://redirect.github.com/nodejs/node/commit/0a4a8d602a)] - **doc**: fix duplicate 'the' typo in `node_platform.cc` (Muhammad Al-Muzahid) [#&#8203;66058](https://redirect.github.com/nodejs/node/pull/66058)
- \[[`b6538138ef`](https://redirect.github.com/nodejs/node/commit/b6538138ef)] - **doc**: clarify sub-1000ms behavior in socket.setKeepAlive (Haram Jeong) [#&#8203;65869](https://redirect.github.com/nodejs/node/pull/65869)
- \[[`d319fa7de4`](https://redirect.github.com/nodejs/node/commit/d319fa7de4)] - **doc**: remove obsolete mentioning of cl.exe on windows (Chengzhong Wu) [#&#8203;66038](https://redirect.github.com/nodejs/node/pull/66038)
- \[[`67cb305b59`](https://redirect.github.com/nodejs/node/commit/67cb305b59)] - **doc**: note that default signal handling resets the signal mask (Shelley Vohr) [#&#8203;65877](https://redirect.github.com/nodejs/node/pull/65877)
- \[[`160d44e025`](https://redirect.github.com/nodejs/node/commit/160d44e025)] - **doc**: clarify QUIC async write backpressure (John Finnerty) [#&#8203;65947](https://redirect.github.com/nodejs/node/pull/65947)
- \[[`ffd47a7ee5`](https://redirect.github.com/nodejs/node/commit/ffd47a7ee5)] - **doc**: clarify permission model scope for output paths (Rafael Gonzaga) [#&#8203;66004](https://redirect.github.com/nodejs/node/pull/66004)
- \[[`2e1772a8ab`](https://redirect.github.com/nodejs/node/commit/2e1772a8ab)] - **doc**: fill in missing zstd docs (James M Snell) [#&#8203;65867](https://redirect.github.com/nodejs/node/pull/65867)
- \[[`9f88c6f8dc`](https://redirect.github.com/nodejs/node/commit/9f88c6f8dc)] - **doc**: add inoway46 as triager (Yuya Inoue) [#&#8203;65565](https://redirect.github.com/nodejs/node/pull/65565)
- \[[`c2620d01f3`](https://redirect.github.com/nodejs/node/commit/c2620d01f3)] - **doc**: document windowsHide for child_process.fork (Christopher Buss) [#&#8203;65887](https://redirect.github.com/nodejs/node/pull/65887)
- \[[`6076111e1a`](https://redirect.github.com/nodejs/node/commit/6076111e1a)] - **doc**: qualify directory read ordering for native fs (Trivikram Kamat) [#&#8203;65868](https://redirect.github.com/nodejs/node/pull/65868)
- \[[`7bf048a336`](https://redirect.github.com/nodejs/node/commit/7bf048a336)] - **doc**: add DOMException section to errors API reference (Avocado) [#&#8203;65206](https://redirect.github.com/nodejs/node/pull/65206)
- \[[`414b6bfa62`](https://redirect.github.com/nodejs/node/commit/414b6bfa62)] - **doc**: expand revert commit collaborator instructions (Mike McCready) [#&#8203;65848](https://redirect.github.com/nodejs/node/pull/65848)
- \[[`c8ac91692c`](https://redirect.github.com/nodejs/node/commit/c8ac91692c)] - **doc**: note that FreeEnvironment() runs a shared event loop (Shelley Vohr) [#&#8203;65691](https://redirect.github.com/nodejs/node/pull/65691)
- \[[`cc47cc0983`](https://redirect.github.com/nodejs/node/commit/cc47cc0983)] - **doc**: clarify security triage dispositions and permission boundaries (Rafael Gonzaga) [#&#8203;65436](https://redirect.github.com/nodejs/node/pull/65436)
- \[[`8b79a71e78`](https://redirect.github.com/nodejs/node/commit/8b79a71e78)] - **doc**: fix default limit of maxHeadersCount (Aryn H) [#&#8203;65472](https://redirect.github.com/nodejs/node/pull/65472)
- \[[`56b1252a54`](https://redirect.github.com/nodejs/node/commit/56b1252a54)] - **doc**: fix return types for sync methods (Chiang Fong Lee) [#&#8203;58575](https://redirect.github.com/nodejs/node/pull/58575)
- \[[`da0d32a333`](https://redirect.github.com/nodejs/node/commit/da0d32a333)] - **doc**: document REPL DEP0185 throws and uncaught-exception behavior (Adrián Estrada) [#&#8203;64993](https://redirect.github.com/nodejs/node/pull/64993)
- \[[`f8ab3619b6`](https://redirect.github.com/nodejs/node/commit/f8ab3619b6)] - **doc**: document close() error when in a sqlite callback (Trivikram Kamat) [#&#8203;65090](https://redirect.github.com/nodejs/node/pull/65090)
- \[[`131f1db302`](https://redirect.github.com/nodejs/node/commit/131f1db302)] - **doc**: clarify return type of `fs.mkdtemp*` (Antoine du Hamel) [#&#8203;65743](https://redirect.github.com/nodejs/node/pull/65743)
- \[[`7755e98183`](https://redirect.github.com/nodejs/node/commit/7755e98183)] - **doc**: update `changelog-maker` instructions for releasing (Juan José) [#&#8203;65707](https://redirect.github.com/nodejs/node/pull/65707)
- \[[`f982a53433`](https://redirect.github.com/nodejs/node/commit/f982a53433)] - **doc**: add stability status to `crypto.setEngine` (Antoine du Hamel) [#&#8203;65746](https://redirect.github.com/nodejs/node/pull/65746)
- \[[`17be003f7c`](https://redirect.github.com/nodejs/node/commit/17be003f7c)] - **doc**: remove outdated TLS authorized warning (Tim Perry) [#&#8203;65597](https://redirect.github.com/nodejs/node/pull/65597)
- \[[`424002232d`](https://redirect.github.com/nodejs/node/commit/424002232d)] - **doc**: fix stale TOC in maintaining-dependencies (greenhead) [#&#8203;65523](https://redirect.github.com/nodejs/node/pull/65523)
- \[[`dddb9b57a5`](https://redirect.github.com/nodejs/node/commit/dddb9b57a5)] - **doc**: refactor the AI guidelines (Joyee Cheung) [#&#8203;65269](https://redirect.github.com/nodejs/node/pull/65269)
- \[[`397b58e113`](https://redirect.github.com/nodejs/node/commit/397b58e113)] - **doc**: fix triggerAsyncId() comment in async_hooks example (soreavis) [#&#8203;64583](https://redirect.github.com/nodejs/node/pull/64583)
- \[[`338ec94d83`](https://redirect.github.com/nodejs/node/commit/338ec94d83)] - **doc**: fix fsPromises.watch overflow value (Matt Radbourne) [#&#8203;64605](https://redirect.github.com/nodejs/node/pull/64605)
- \[[`f18be21b14`](https://redirect.github.com/nodejs/node/commit/f18be21b14)] - **doc**: clarify stream direction in options.stdio note (Avocado) [#&#8203;65236](https://redirect.github.com/nodejs/node/pull/65236)
- \[[`f07c6acd8b`](https://redirect.github.com/nodejs/node/commit/f07c6acd8b)] - **doc**: clarify signal listener behavior (Som Samantray) [#&#8203;65243](https://redirect.github.com/nodejs/node/pull/65243)
- \[[`8bc17b0c7a`](https://redirect.github.com/nodejs/node/commit/8bc17b0c7a)] - **doc**: add test reporter event lifecycle diagram (sangwook) [#&#8203;63780](https://redirect.github.com/nodejs/node/pull/63780)
- \[[`452e7477d4`](https://redirect.github.com/nodejs/node/commit/452e7477d4)] - **doc**: discourage AbortSignal cleanup for long-lived resources (Efe Karasakal) [#&#8203;64342](https://redirect.github.com/nodejs/node/pull/64342)
- \[[`acc1578179`](https://redirect.github.com/nodejs/node/commit/acc1578179)] - **doc**: deprecate `Server.prototype._listen2` in `node:net` (Antoine du Hamel) [#&#8203;65593](https://redirect.github.com/nodejs/node/pull/65593)
- \[[`e7ed4361d6`](https://redirect.github.com/nodejs/node/commit/e7ed4361d6)] - **doc,test**: account for OpenSSL 4.1 behaviours (Filip Skokan) [#&#8203;65956](https://redirect.github.com/nodejs/node/pull/65956)
- \[[`c65fafeab4`](https://redirect.github.com/nodejs/node/commit/c65fafeab4)] - **errors**: validate constructor name (Christian Aurich) [#&#8203;65607](https://redirect.github.com/nodejs/node/pull/65607)
- \[[`f6b47938b7`](https://redirect.github.com/nodejs/node/commit/f6b47938b7)] - **events**: fix weak listener retention overwrite (Aryan) [#&#8203;64024](https://redirect.github.com/nodejs/node/pull/64024)
- \[[`a27db42c3e`](https://redirect.github.com/nodejs/node/commit/a27db42c3e)] - **fs**: coerce FileHandle.read length like fs.read (Xia Chao) [#&#8203;65521](https://redirect.github.com/nodejs/node/pull/65521)
- \[[`f3019b522a`](https://redirect.github.com/nodejs/node/commit/f3019b522a)] - **fs**: throw on existing dir in cpSync with errorOnExist (Daijiro Wachi) [#&#8203;64124](https://redirect.github.com/nodejs/node/pull/64124)
- \[[`cd1011458f`](https://redirect.github.com/nodejs/node/commit/cd1011458f)] - **fs**: support removing read-only files in rmSync on Windows (Sparsh :)) [#&#8203;64453](https://redirect.github.com/nodejs/node/pull/64453)
- \[[`d659dd8cd8`](https://redirect.github.com/nodejs/node/commit/d659dd8cd8)] - **fs**: give directories created by cpSync the source directory's mode (Shelley Vohr) [#&#8203;65488](https://redirect.github.com/nodejs/node/pull/65488)
- \[[`1dfb047293`](https://redirect.github.com/nodejs/node/commit/1dfb047293)] - **fs**: handle recursive watch setup races (Filip Skokan) [#&#8203;65699](https://redirect.github.com/nodejs/node/pull/65699)
- \[[`bc431c8413`](https://redirect.github.com/nodejs/node/commit/bc431c8413)] - **fs**: apply nocase to literal glob exclude patterns (Yusuke Hayashi) [#&#8203;64817](https://redirect.github.com/nodejs/node/pull/64817)
- \[[`dd5662e641`](https://redirect.github.com/nodejs/node/commit/dd5662e641)] - **fs**: do not descend into symlinks for \*\* unless following symlinks (RafaelGSS) [#&#8203;65435](https://redirect.github.com/nodejs/node/pull/65435)
- \[[`ac4cf54292`](https://redirect.github.com/nodejs/node/commit/ac4cf54292)] - **fs**: watch directories, not files, in recursive fs.watch fallback (Shelley Vohr) [#&#8203;65486](https://redirect.github.com/nodejs/node/pull/65486)
- \[[`7804bfbd71`](https://redirect.github.com/nodejs/node/commit/7804bfbd71)] - **fs**: preserve directory timestamps in cp (Abhinandan Kumar) [#&#8203;65540](https://redirect.github.com/nodejs/node/pull/65540)
- \[[`ba5ca5a1a5`](https://redirect.github.com/nodejs/node/commit/ba5ca5a1a5)] - **fs**: fix recursive watch error handling (Filip Skokan) [#&#8203;65635](https://redirect.github.com/nodejs/node/pull/65635)
- \[[`b71c4c0551`](https://redirect.github.com/nodejs/node/commit/b71c4c0551)] - **fs**: fix rmSync error messages for non-ASCII paths (Yeaseen) [#&#8203;61233](https://redirect.github.com/nodejs/node/pull/61233)
- \[[`53b4700892`](https://redirect.github.com/nodejs/node/commit/53b4700892)] - **http**: don't destroy socket after request completes (Barath Raj) [#&#8203;65952](https://redirect.github.com/nodejs/node/pull/65952)
- \[[`96d4c23f17`](https://redirect.github.com/nodejs/node/commit/96d4c23f17)] - **http**: fast-forward teardown of unread messages (Matteo Collina) [#&#8203;65732](https://redirect.github.com/nodejs/node/pull/65732)
- \[[`742b4bf5a6`](https://redirect.github.com/nodejs/node/commit/742b4bf5a6)] - **http**: coalesce chunked writes during auto-corking (GetThatCookie) [#&#8203;64987](https://redirect.github.com/nodejs/node/pull/64987)
- \[[`8921108175`](https://redirect.github.com/nodejs/node/commit/8921108175)] - **http2**: settle pending write callbacks on destroy (Matteo Collina) [#&#8203;66016](https://redirect.github.com/nodejs/node/pull/66016)
- \[[`9a2f77a3fc`](https://redirect.github.com/nodejs/node/commit/9a2f77a3fc)] - **http2**: fix onread assert when destroying session from stream handler (Sankalp Thakur) [#&#8203;65116](https://redirect.github.com/nodejs/node/pull/65116)
- \[[`99e41eb43a`](https://redirect.github.com/nodejs/node/commit/99e41eb43a)] - **http2**: error for incomplete reads on RST, auto-drain, deprecate aborted (Tim Perry) [#&#8203;63249](https://redirect.github.com/nodejs/node/pull/63249)
- \[[`d520b9601c`](https://redirect.github.com/nodejs/node/commit/d520b9601c)] - **http2**: fix write deadlock exposed by larger window sizes (Tim Perry) [#&#8203;65440](https://redirect.github.com/nodejs/node/pull/65440)
- \[[`86b2497aae`](https://redirect.github.com/nodejs/node/commit/86b2497aae)] - **http2**: fix async context loss when trailers carry END_STREAM (Orgad Shaneh) [#&#8203;63814](https://redirect.github.com/nodejs/node/pull/63814)
- \[[`536ccc7daf`](https://redirect.github.com/nodejs/node/commit/536ccc7daf)] - **https**: limit proxy CONNECT response headers (Matteo Collina) [#&#8203;64545](https://redirect.github.com/nodejs/node/pull/64545)
- \[[`fe6147832b`](https://redirect.github.com/nodejs/node/commit/fe6147832b)] - **inspector**: fix abort when two Environments own the inspector (Shelley Vohr) [#&#8203;65877](https://redirect.github.com/nodejs/node/pull/65877)
- \[[`843a27782a`](https://redirect.github.com/nodejs/node/commit/843a27782a)] - **inspector**: fix crash when the IsolateData has no platform (Shelley Vohr) [#&#8203;65818](https://redirect.github.com/nodejs/node/pull/65818)
- \[[`3f0b6d3308`](https://redirect.github.com/nodejs/node/commit/3f0b6d3308)] - **lib**: avoid repeat internal receiver checks (Filip Skokan) [#&#8203;65910](https://redirect.github.com/nodejs/node/pull/65910)
- \[[`0039479b4a`](https://redirect.github.com/nodejs/node/commit/0039479b4a)] - **lib**: fix shared buffer growability validation (Filip Skokan) [#&#8203;65845](https://redirect.github.com/nodejs/node/pull/65845)
- \[[`e7907130ae`](https://redirect.github.com/nodejs/node/commit/e7907130ae)] - **lib**: use Web IDL interface brand checks (Filip Skokan) [#&#8203;65846](https://redirect.github.com/nodejs/node/pull/65846)
- \[[`7ae085552b`](https://redirect.github.com/nodejs/node/commit/7ae085552b)] - **lib**: fix AbortSignal.any() abort propagation (Yuya Inoue) [#&#8203;66014](https://redirect.github.com/nodejs/node/pull/66014)
- \[[`01032f4fdc`](https://redirect.github.com/nodejs/node/commit/01032f4fdc)] - **lib**: fix for FileHandle.readableWebStream (Patrick Dähne) [#&#8203;58842](https://redirect.github.com/nodejs/node/pull/58842)
- \[[`60a9a28206`](https://redirect.github.com/nodejs/node/commit/60a9a28206)] - **lib**: avoid unsafe array iteration in cli table (Donghoon Kang) [#&#8203;65838](https://redirect.github.com/nodejs/node/pull/65838)
- \[[`230a7fcef0`](https://redirect.github.com/nodejs/node/commit/230a7fcef0)] - **lib**: optimize internal Web IDL dictionaries (Filip Skokan) [#&#8203;65857](https://redirect.github.com/nodejs/node/pull/65857)
- \[[`7cc02b14ec`](https://redirect.github.com/nodejs/node/commit/7cc02b14ec)] - **lib**: validate sequence iterator objects (Filip Skokan) [#&#8203;65844](https://redirect.github.com/nodejs/node/pull/65844)
- \[[`c2bb3aeccc`](https://redirect.github.com/nodejs/node/commit/c2bb3aeccc)] - **lib**: defer source map payload decoding until first use (Shelley Vohr) [#&#8203;65490](https://redirect.github.com/nodejs/node/pull/65490)
- \[[`c4b390cf17`](https://redirect.github.com/nodejs/node/commit/c4b390cf17)] - **lib**: optimize async context frame activation (Tim Perry) [#&#8203;65519](https://redirect.github.com/nodejs/node/pull/65519)
- \[[`3e4fc50a4f`](https://redirect.github.com/nodejs/node/commit/3e4fc50a4f)] - **lib**: use validateArray for array arguments (JunHwan Choi) [#&#8203;65344](https://redirect.github.com/nodejs/node/pull/65344)
- \[[`b351ac9cd4`](https://redirect.github.com/nodejs/node/commit/b351ac9cd4)] - **meta**: bump step-security/harden-runner from 2.21.0 to 2.21.1 (dependabot\[bot]) [#&#8203;66044](https://redirect.github.com/nodejs/node/pull/66044)
- \[[`693e890fec`](https://redirect.github.com/nodejs/node/commit/693e890fec)] - **meta**: bump github/codeql-action/upload-sarif from 4.37.9 to 4.38.0 (dependabot\[bot]) [#&#8203;66046](https://redirect.github.com/nodejs/node/pull/66046)
- \[[`9c2abc13c2`](https://redirect.github.com/nodejs/node/commit/9c2abc13c2)] - **meta**: bump github/codeql-action/init from 4.37.9 to 4.38.0 (dependabot\[bot]) [#&#8203;66048](https://redirect.github.com/nodejs/node/pull/66048)
- \[[`b3c5c2b91d`](https://redirect.github.com/nodejs/node/commit/b3c5c2b91d)] - **meta**: bump github/codeql-action/analyze from 4.37.9 to 4.38.0 (dependabot\[bot]) [#&#8203;66049](https://redirect.github.com/nodejs/node/pull/66049)
- \[[`2428b13b02`](https://redirect.github.com/nodejs/node/commit/2428b13b02)] - **meta**: bump github/codeql-action/autobuild from 4.37.9 to 4.38.0 (dependabot\[bot]) [#&#8203;66050](https://redirect.github.com/nodejs/node/pull/66050)
- \[[`eff5d15041`](https://redirect.github.com/nodejs/node/commit/eff5d15041)] - **meta**: add joyeecheung as v8 currency strategic initiative champion (Joyee Cheung) [#&#8203;65965](https://redirect.github.com/nodejs/node/pull/65965)
- \[[`aa549fed0d`](https://redirect.github.com/nodejs/node/commit/aa549fed0d)] - **meta**: expand on collaborator restoration process (Chengzhong Wu) [#&#8203;65962](https://redirect.github.com/nodejs/node/pull/65962)
- \[[`593b40eb2b`](https://redirect.github.com/nodejs/node/commit/593b40eb2b)] - **meta**: add web-standards as webidl owners (Filip Skokan) [#&#8203;65856](https://redirect.github.com/nodejs/node/pull/65856)
- \[[`e711d3fef3`](https://redirect.github.com/nodejs/node/commit/e711d3fef3)] - **meta**: bump actions/checkout from 7.0.0 to 7.0.1 (dependabot\[bot]) [#&#8203;65718](https://redirect.github.com/nodejs/node/pull/65718)
- \[[`0fc8a38f5b`](https://redirect.github.com/nodejs/node/commit/0fc8a38f5b)] - **meta**: cleanup targos emeritus changes (Antoine du Hamel) [#&#8203;65738](https://redirect.github.com/nodejs/node/pull/65738)
- \[[`2ca2fd566d`](https://redirect.github.com/nodejs/node/commit/2ca2fd566d)] - **meta**: bump github/codeql-action/init from 4.37.3 to 4.37.9 (dependabot\[bot]) [#&#8203;65714](https://redirect.github.com/nodejs/node/pull/65714)
- \[[`325709818e`](https://redirect.github.com/nodejs/node/commit/325709818e)] - **meta**: bump github/codeql-action/autobuild from 4.37.3 to 4.37.9 (dependabot\[bot]) [#&#8203;65717](https://redirect.github.com/nodejs/node/pull/65717)
- \[[`8481d22f72`](https://redirect.github.com/nodejs/node/commit/8481d22f72)] - **meta**: bump actions/setup-node from 6.4.0 to 7.0.0 (dependabot\[bot]) [#&#8203;65720](https://redirect.github.com/nodejs/node/pull/65720)
- \[[`cd2bd5d9fd`](https://redirect.github.com/nodejs/node/commit/cd2bd5d9fd)] - **meta**: bump step-security/harden-runner from 2.20.0 to 2.21.0 (dependabot\[bot]) [#&#8203;65721](https://redirect.github.com/nodejs/node/pull/65721)
- \[[`fdec747105`](https://redirect.github.com/nodejs/node/commit/fdec747105)] - **meta**: bump github/codeql-action/upload-sarif from 4.37.3 to 4.37.9 (dependabot\[bot]) [#&#8203;65722](https://redirect.github.com/nodejs/node/pull/65722)
- \[[`34c2fca088`](https://redirect.github.com/nodejs/node/commit/34c2fca088)] - **meta**: bump github/codeql-action/analyze from 4.37.3 to 4.37.9 (dependabot\[bot]) [#&#8203;65723](https://redirect.github.com/nodejs/node/pull/65723)
- \[[`46d4549938`](https://redirect.github.com/nodejs/node/commit/46d4549938)] - **meta**: document collaborator automation (Filip Skokan) [#&#8203;65671](https://redirect.github.com/nodejs/node/pull/65671)
- \[[`a7f8de6362`](https://redirect.github.com/nodejs/node/commit/a7f8de6362)] - **(SEMVER-MINOR)** **net**: support sending net.BoundSocket to threads and child processes (Guy Bedford) [#&#8203;64725](https://redirect.github.com/nodejs/node/pull/64725)
- \[[`466f1f8e62`](https://redirect.github.com/nodejs/node/commit/466f1f8e62)] - **net**: fix BlockList.fromJSON for IPv4-mapped IPv6 rules (Daijiro Wachi) [#&#8203;64125](https://redirect.github.com/nodejs/node/pull/64125)
- \[[`cd04cb10d6`](https://redirect.github.com/nodejs/node/commit/cd04cb10d6)] - **net**: recognize bare IPv6 loopback addresses in isLoopback (Daijiro Wachi) [#&#8203;63619](https://redirect.github.com/nodejs/node/pull/63619)
- \[[`d983942636`](https://redirect.github.com/nodejs/node/commit/d983942636)] - **node-api**: make object property arrays const (Yilong Li) [#&#8203;65621](https://redirect.github.com/nodejs/node/pull/65621)
- \[[`cc124394cc`](https://redirect.github.com/nodejs/node/commit/cc124394cc)] - **path**: remove `StringPrototypeCharCodeAt` from some methods of `posix` (Wiyeong Seo) [#&#8203;54668](https://redirect.github.com/nodejs/node/pull/54668)
- \[[`60c3eb61bd`](https://redirect.github.com/nodejs/node/commit/60c3eb61bd)] - **(SEMVER-MINOR)** **perf_hooks**: implement SlidingWindowHistogram (James M Snell) [#&#8203;65825](https://redirect.github.com/nodejs/node/pull/65825)
- \[[`6b3e6f9d5c`](https://redirect.github.com/nodejs/node/commit/6b3e6f9d5c)] - **(SEMVER-MINOR)** **perf_hooks**: implement qrde analysis support in Histogram (James M Snell) [#&#8203;65806](https://redirect.github.com/nodejs/node/pull/65806)
- \[[`8aa870940c`](https://redirect.github.com/nodejs/node/commit/8aa870940c)] - **perf_hooks**: reuse buffer for uv metrics (Donghoon Kang) [#&#8203;65985](https://redirect.github.com/nodejs/node/pull/65985)
- \[[`9cfbfae660`](https://redirect.github.com/nodejs/node/commit/9cfbfae660)] - **perf_hooks**: validate import normalization offset (Matteo Collina) [#&#8203;65950](https://redirect.github.com/nodejs/node/pull/65950)
- \[[`75456a3512`](https://redirect.github.com/nodejs/node/commit/75456a3512)] - **(SEMVER-MINOR)** **perf_hooks**: implement Histogram meanCI API (James M Snell) [#&#8203;65606](https://redirect.github.com/nodejs/node/pull/65606)
- \[[`127cb31864`](https://redirect.github.com/nodejs/node/commit/127cb31864)] - **perf_hooks**: add missing resource timing attributes (greenhead) [#&#8203;65017](https://redirect.github.com/nodejs/node/pull/65017)
- \[[`72d1b40c56`](https://redirect.github.com/nodejs/node/commit/72d1b40c56)] - **(SEMVER-MINOR)** **perf_hooks**: add CBOR export/import for histogram exchange (James M Snell) [#&#8203;65434](https://redirect.github.com/nodejs/node/pull/65434)
- \[[`847fe4d049`](https://redirect.github.com/nodejs/node/commit/847fe4d049)] - **permission**: block FileHandle fsync and fdatasync (Rafael Gonzaga) [#&#8203;65431](https://redirect.github.com/nodejs/node/pull/65431)
- \[[`fa9188bd63`](https://redirect.github.com/nodejs/node/commit/fa9188bd63)] - **quic**: fix two small bugs in HTTP/3 stream internals (Tim Perry) [#&#8203;65970](https://redirect.github.com/nodejs/node/pull/65970)
- \[[`ee80beaa9f`](https://redirect.github.com/nodejs/node/commit/ee80beaa9f)] - **quic**: fix crash in onStreamClose (Marten Richter) [#&#8203;65861](https://redirect.github.com/nodejs/node/pull/65861)
- \[[`98486dd764`](https://redirect.github.com/nodejs/node/commit/98486dd764)] - **quic**: reject zero addressLRUSize (Christian Aurich) [#&#8203;65827](https://redirect.github.com/nodejs/node/pull/65827)
- \[[`97516fc256`](https://redirect.github.com/nodejs/node/commit/97516fc256)] - **quic**: stop guarding ngtcp2_recv_stop_sending callback field (René) [#&#8203;65688](https://redirect.github.com/nodejs/node/pull/65688)
- \[[`024bef25eb`](https://redirect.github.com/nodejs/node/commit/024bef25eb)] - **quic**: remove unused fin flag from blob reader wakeup (trivenay) [#&#8203;65315](https://redirect.github.com/nodejs/node/pull/65315)
- \[[`5468337456`](https://redirect.github.com/nodejs/node/commit/5468337456)] - **quic**: release stream arenas before cleanup (Trivikram Kamat) [#&#8203;65410](https://redirect.github.com/nodejs/node/pull/65410)
- \[[`a794e36970`](https://redirect.github.com/nodejs/node/commit/a794e36970)] - **sqlite**: throw on invalid URL path instead of abort (Guilherme Araújo) [#&#8203;66026](https://redirect.github.com/nodejs/node/pull/66026)
- \[[`f58a55b8d0`](https://redirect.github.com/nodejs/node/commit/f58a55b8d0)] - **sqlite**: add diagnostic channel (Guilherme Araújo) [#&#8203;62241](https://redirect.github.com/nodejs/node/pull/62241)
- \[[`be849bc6af`](https://redirect.github.com/nodejs/node/commit/be849bc6af)] - **sqlite**: reject connection access from authorizer callbacks (Trevor Burnham) [#&#8203;65156](https://redirect.github.com/nodejs/node/pull/65156)
- \[[`41161dda87`](https://redirect.github.com/nodejs/node/commit/41161dda87)] - **(SEMVER-MINOR)** **sqlite**: add StatementSync.prototype.close() (Guilherme Araújo) [#&#8203;64232](https://redirect.github.com/nodejs/node/pull/64232)
- \[[`adbc757d70`](https://redirect.github.com/nodejs/node/commit/adbc757d70)] - **(SEMVER-MINOR)** **sqlite**: add StatementSync.prototype\[Symbol.dispose]\() (Guilherme Araújo) [#&#8203;64232](https://redirect.github.com/nodejs/node/pull/64232)
- \[[`b7b18fdbc9`](https://redirect.github.com/nodejs/node/commit/b7b18fdbc9)] - **sqlite**: run backup completion in callback scope (Filip Skokan) [#&#8203;65666](https://redirect.github.com/nodejs/node/pull/65666)
- \[[`59827acd7f`](https://redirect.github.com/nodejs/node/commit/59827acd7f)] - **sqlite**: isolate applyChangeset filter errors (Trivikram Kamat) [#&#8203;64823](https://redirect.github.com/nodejs/node/pull/64823)
- \[[`5a01df2d15`](https://redirect.github.com/nodejs/node/commit/5a01df2d15)] - **sqlite**: reject deserialize() while in a callback (Trivikram Kamat) [#&#8203;64796](https://redirect.github.com/nodejs/node/pull/64796)
- \[[`c9b6e75a2e`](https://redirect.github.com/nodejs/node/commit/c9b6e75a2e)] - **sqlite**: refactor error helpers and user function pointers (Ali Hassan) [#&#8203;62794](https://redirect.github.com/nodejs/node/pull/62794)
- \[[`d4eb6c62a9`](https://redirect.github.com/nodejs/node/commit/d4eb6c62a9)] - **sqlite**: prevent database close during callbacks (Matteo Collina) [#&#8203;64743](https://redirect.github.com/nodejs/node/pull/64743)
- \[[`83b3431c25`](https://redirect.github.com/nodejs/node/commit/83b3431c25)] - **src**: detach cppgc wrappers from their Realm before it is freed (Shelley Vohr) [#&#8203;65778](https://redirect.github.com/nodejs/node/pull/65778)
- \[[`13ed7e35e7`](https://redirect.github.com/nodejs/node/commit/13ed7e35e7)] - **src**: list scripts when `--run` has no command (James Ross) [#&#8203;64606](https://redirect.github.com/nodejs/node/pull/64606)
- \[[`0f62f2b2df`](https://redirect.github.com/nodejs/node/commit/0f62f2b2df)] - **src**: print exception thrown during primordial initialization (Joyee Cheung) [#&#8203;65991](https://redirect.github.com/nodejs/node/pull/65991)
- \[[`70117640f7`](https://redirect.github.com/nodejs/node/commit/70117640f7)] - **src**: don't kill own process group on failed spawn (Lazizbek Ergashev) [#&#8203;65054](https://redirect.github.com/nodejs/node/pull/65054)
- \[[`64649abfd4`](https://redirect.github.com/nodejs/node/commit/64649abfd4)] - **src**: keep the first snapshot blob alive for later isolates (Shelley Vohr) [#&#8203;65779](https://redirect.github.com/nodejs/node/pull/65779)
- \[[`3c814e8c3c`](https://redirect.github.com/nodejs/node/commit/3c814e8c3c)] - **src**: fix Stop() terminating the next Environment on the isolate (Shelley Vohr) [#&#8203;65819](https://redirect.github.com/nodejs/node/pull/65819)
- \[[`a3ababcebb`](https://redirect.github.com/nodejs/node/commit/a3ababcebb)] - **src**: fix null pointer call when running without a startup snapshot (Shelley Vohr) [#&#8203;65820](https://redirect.github.com/nodejs/node/pull/65820)
- \[[`7331f5cfcc`](https://redirect.github.com/nodejs/node/commit/7331f5cfcc)] - **src**: stop leaking a CppHeap in CommonEnvironmentSetup (Shelley Vohr) [#&#8203;65792](https://redirect.github.com/nodejs/node/pull/65792)
- \[[`5570a84048`](https://redirect.github.com/nodejs/node/commit/5570a84048)] - **src**: fix use-after-free in CleanupHookThunkRun (Caleb Everett) [#&#8203;65630](https://redirect.github.com/nodejs/node/pull/65630)
- \[[`abe1b673e9`](https://redirect.github.com/nodejs/node/commit/abe1b673e9)] - **src**: keep global list of addon-provided cleanup hooks (Anna Henningsen) [#&#8203;63985](https://redirect.github.com/nodejs/node/pull/63985)
- \[[`2203ac9382`](https://redirect.github.com/nodejs/node/commit/2203ac9382)] - **src**: fix startup snapshot reproducibility of InternalFieldInfo (Chengzhong Wu) [#&#8203;65684](https://redirect.github.com/nodejs/node/pull/65684)
- \[[`b282765d99`](https://redirect.github.com/nodejs/node/commit/b282765d99)] - **src**: fix live lock between environments with blocked requests (Ilyas Shabi) [#&#8203;65520](https://redirect.github.com/nodejs/node/pull/65520)
- \[[`61ca3ba93c`](https://redirect.github.com/nodejs/node/commit/61ca3ba93c)] - **src**: apply IsolateSettings when using a snapshot (Shelley Vohr) [#&#8203;65407](https://redirect.github.com/nodejs/node/pull/65407)
- \[[`33b8d6e10f`](https://redirect.github.com/nodejs/node/commit/33b8d6e10f)] - **src**: add re-entrancy guard to TriggerUncaughtException (Temuulen Undrakhbayar) [#&#8203;64327](https://redirect.github.com/nodejs/node/pull/64327)
- \[[`49232db71e`](https://redirect.github.com/nodejs/node/commit/49232db71e)] - **src**: reuse cached strings in CompileSerializeMain (agape1225) [#&#8203;65453](https://redirect.github.com/nodejs/node/pull/65453)
- \[[`e3f52b80a5`](https://redirect.github.com/nodejs/node/commit/e3f52b80a5)] - **src**: report libuv error when openAsBlob cannot stat (Paul Bouchon) [#&#8203;65517](https://redirect.github.com/nodejs/node/pull/65517)
- \[[`42a5a19027`](https://redirect.github.com/nodejs/node/commit/42a5a19027)] - **src**: shave about 20 bytes off each TLSWrap instance (James M Snell) [#&#8203;65144](https://redirect.github.com/nodejs/node/pull/65144)
- \[[`a06bb51e22`](https://redirect.github.com/nodejs/node/commit/a06bb51e22)] - **src**: use UTF-8 for task runner filesystem paths (Archkon) [#&#8203;64868](https://redirect.github.com/nodejs/node/pull/64868)
- \[[`241339f072`](https://redirect.github.com/nodejs/node/commit/241339f072)] - **(SEMVER-MINOR)** **src,lib**: add util.markPromiseAsHandled (James M Snell) [#&#8203;65805](https://redirect.github.com/nodejs/node/pull/65805)
- \[[`1260642ace`](https://redirect.github.com/nodejs/node/commit/1260642ace)] - **stream**: destroy Duplex.from async function on early return (Aman Chadha(IVIXMMI)) [#&#8203;65963](https://redirect.github.com/nodejs/node/pull/65963)
- \[[`5f06defdbd`](https://redirect.github.com/nodejs/node/commit/5f06defdbd)] - **stream**: make share budget failures detach before throwing (James M Snell) [#&#8203;66028](https://redirect.github.com/nodejs/node/pull/66028)
- \[[`d1c60d898d`](https://redirect.github.com/nodejs/node/commit/d1c60d898d)] - **stream**: update broadcast to retain buffered data with zero consumers (James M Snell) [#&#8203;66028](https://redirect.github.com/nodejs/node/pull/66028)
- \[[`bbb00558c5`](https://redirect.github.com/nodejs/node/commit/bbb00558c5)] - **stream**: avoid promise allocation for parked transform writes (Matteo Collina) [#&#8203;65625](https://redirect.github.com/nodejs/node/pull/65625)
- \[[`3216bc3912`](https://redirect.github.com/nodejs/node/commit/3216bc3912)] - **stream**: keep webstream stream states in fast-mode objects (Matteo Collina) [#&#8203;65625](https://redirect.github.com/nodejs/node/pull/65625)
- \[[`70c1415686`](https://redirect.github.com/nodejs/node/commit/70c1415686)] - **stream**: reject closed only after sink abort settles (Lazizbek Ergashev) [#&#8203;65727](https://redirect.github.com/nodejs/node/pull/65727)
- \[[`f8737b8ee5`](https://redirect.github.com/nodejs/node/commit/f8737b8ee5)] - **stream**: fix ERR_INVALID_STATE when cancelling Readable.toWeb() (Richard Scarrott) [#&#8203;62773](https://redirect.github.com/nodejs/node/pull/62773)
- \[[`faa16f671c`](https://redirect.github.com/nodejs/node/commit/faa16f671c)] - **stream**: amortize writable buffer compaction (Gürgün Dayıoğlu) [#&#8203;65847](https://redirect.github.com/nodejs/node/pull/65847)
- \[[`6262e0b8c6`](https://redirect.github.com/nodejs/node/commit/6262e0b8c6)] - **stream**: create write request objects lazily (Matteo Collina) [#&#8203;64455](https://redirect.github.com/nodejs/node/pull/64455)
- \[[`0f0566de15`](https://redirect.github.com/nodejs/node/commit/0f0566de15)] - **stream**: allocate stream read buffers from a slab (Matteo Collina) [#&#8203;64455](https://redirect.github.com/nodejs/node/pull/64455)
- \[[`02c776a7c6`](https://redirect.github.com/nodejs/node/commit/02c776a7c6)] - **stream**: ensure that stateful transforms preserve this (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`4b29051971`](https://redirect.github.com/nodejs/node/commit/4b29051971)] - **stream**: fix nested async flushing with infinite sources (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`2e54186d97`](https://redirect.github.com/nodejs/node/commit/2e54186d97)] - **stream**: ensure from() observes returned rejecting promise correctly (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`fd68d5ca6a`](https://redirect.github.com/nodejs/node/commit/fd68d5ca6a)] - **stream**: apply source normalization once at call time (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`49ee85b462`](https://redirect.github.com/nodejs/node/commit/49ee85b462)] - **stream**: make pipeTo source normalization independent of Writer (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`d7d0440f08`](https://redirect.github.com/nodejs/node/commit/d7d0440f08)] - **stream**: pre-aborted pipeTo now applies dest failure handling (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`6d8e643ef3`](https://redirect.github.com/nodejs/node/commit/6d8e643ef3)] - **stream**: ensure async dispoal after endSync awaits for drain (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`a082916c00`](https://redirect.github.com/nodejs/node/commit/a082916c00)] - **stream**: ensure factory signals remain active through closing (James M Snell) [#&#8203;65658](https://redirect.github.com/nodejs/node/pull/65658)
- \[[`38829295a7`](https://redirect.github.com/nodejs/node/commit/38829295a7)] - **stream**: skip unobserved 'readable' emission at EOF (Matteo Collina) [#&#8203;65749](https://redirect.github.com/nodejs/node/pull/65749)
- \[[`d535859c22`](https://redirect.github.com/nodejs/node/commit/d535859c22)] - **stream**: avoid per-chunk promises in webstream adapters (Matteo Collina) [#&#8203;65548](https://redirect.github.com/nodejs/node/pull/65548)
- \[[`80568f6818`](https://redirect.github.com/nodejs/node/commit/80568f6818)] - **stream**: address stream/iter review feedback (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`6952928f68`](https://redirect.github.com/nodejs/node/commit/6952928f68)] - **stream**: replace object sentinel with symbol (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`9c22c4124c`](https://redirect.github.com/nodejs/node/commit/9c22c4124c)] - **stream**: cancel active stream/iter pulls (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`24d87990cf`](https://redirect.github.com/nodejs/node/commit/24d87990cf)] - **stream**: fix merge settlement tagging and falsy error tracking (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`1e215c4b51`](https://redirect.github.com/nodejs/node/commit/1e215c4b51)] - **stream**: ensure full-close semantics when closed (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`f499d6fadf`](https://redirect.github.com/nodejs/node/commit/f499d6fadf)] - **stream**: fixup writer to terminate on consumer return/throw (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`2130738033`](https://redirect.github.com/nodejs/node/commit/2130738033)] - **stream**: ensure iterator cleanup on done, reject, etc (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`748adc6275`](https://redirect.github.com/nodejs/node/commit/748adc6275)] - **stream**: fixup cancelation handling in pull() (James M Snell) [#&#8203;65652](https://redirect.github.com/nodejs/node/pull/65652)
- \[[`47b3f6a1fa`](https://redirect.github.com/nodejs/node/commit/47b3f6a1fa)] - **stream**: fix early drain after Utf8Stream reopen (Matteo Collina) [#&#8203;65633](https://redirect.github.com/nodejs/node/pull/65633)
- \[[`6dab5412cf`](https://redirect.github.com/nodejs/node/commit/6dab5412cf)] - **test**: skip RSA-PSS keygen deprecation tests with BoringSSL (Shelley Vohr) [#&#8203;65808](https://redirect.github.com/nodejs/node/pull/65808)
- \[[`95d9a25b61`](https://redirect.github.com/nodejs/node/commit/95d9a25b61)] - **test**: consolidate crypto provider cache coverage (Filip Skokan) [#&#8203;66108](https://redirect.github.com/nodejs/node/pull/66108)
- \[[`612db6d5b2`](https://redirect.github.com/nodejs/node/commit/612db6d5b2)] - **test**: deflake user timing WPT assertions (Filip Skokan) [#&#8203;66036](https://redirect.github.com/nodejs/node/pull/66036)
- \[[`61a73d31eb`](https://redirect.github.com/nodejs/node/commit/61a73d31eb)] - **test**: fix RSA/DSA wrong-passphrase flake (Filip Skokan) [#&#8203;65983](https://redirect.github.com/nodejs/node/pull/65983)
- \[[`d9f4a3ee3e`](https://redirect.github.com/nodejs/node/commit/d9f4a3ee3e)] - **test**: reuse fixed primes in DH tests (Filip Skokan) [#&#8203;65980](https://redirect.github.com/nodejs/node/pull/65980)
- \[[`a0fd0a8bd6`](https://redirect.github.com/nodejs/node/commit/a0fd0a8bd6)] - **test**: use named parameters in DH stress test (Filip Skokan) [#&#8203;65980](https://redi

> [!IMPORTANT]
> ✂ PR body was truncated to here.


</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:27:12 -07:00
renovate[bot] a98e5bad6c chore(deps): update lint (#725)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [oxfmt](https://oxc.rs/docs/guide/usage/formatter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt)) | [`0.67.0` → `0.73.0`](https://renovatebot.com/diffs/npm/oxfmt/0.67.0/0.73.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxfmt/0.73.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxfmt/0.67.0/0.73.0?slim=true) |
| [oxlint](https://oxc.rs/docs/guide/usage/linter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint)) | [`1.82.0` → `1.88.0`](https://renovatebot.com/diffs/npm/oxlint/1.82.0/1.88.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.88.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.82.0/1.88.0?slim=true) |
| [prettier](https://prettier.io) ([source](https://redirect.github.com/prettier/prettier)) | [`3.9.6` → `3.9.10`](https://renovatebot.com/diffs/npm/prettier/3.9.6/3.9.10) | ![age](https://developer.mend.io/api/mc/badges/age/npm/prettier/3.9.10?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/prettier/3.9.6/3.9.10?slim=true) |

---

### Release Notes

<details>
<summary>oxc-project/oxc (oxfmt)</summary>

### [`v0.73.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxfmt/CHANGELOG.md#0730---2026-10-13)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.72.0...oxfmt_v0.73.0)

##### 🚀 Features

- [`a495e82`](https://redirect.github.com/oxc-project/oxc/commit/a495e82) oxfmt: Support `associations` config to specify language over filepath ([#&#8203;27437](https://redirect.github.com/oxc-project/oxc/issues/27437)) (leaysgur)
- [`5db28fa`](https://redirect.github.com/oxc-project/oxc/commit/5db28fa) oxfmt: Support Astro via `[email protected]` ([#&#8203;27386](https://redirect.github.com/oxc-project/oxc/issues/27386)) (leaysgur)

##### 📚 Documentation

- [`408d87f`](https://redirect.github.com/oxc-project/oxc/commit/408d87f) oxfmt: Simplify oxfmtrc docs ([#&#8203;27432](https://redirect.github.com/oxc-project/oxc/issues/27432)) (leaysgur)
- [`902e188`](https://redirect.github.com/oxc-project/oxc/commit/902e188) oxfmt: Update how `embeddedLanguageFormatting` option works ([#&#8203;27362](https://redirect.github.com/oxc-project/oxc/issues/27362)) (leaysgur)

### [`v0.72.0`](https://redirect.github.com/oxc-project/oxc/releases/tag/oxfmt_v0.72.0): oxfmt v0.72.0

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.71.0...oxfmt_v0.72.0)

##### 💥 BREAKING CHANGES

- [`e2c68b1`](https://redirect.github.com/oxc-project/oxc/commit/e2c68b1) oxfmt: \[**BREAKING**] Format `parser:markdown` files by `oxc_formatter_markdown` ([#&#8203;27256](https://redirect.github.com/oxc-project/oxc/issues/27256)) (leaysgur)

##### 🚀 Features

- [`b538e3c`](https://redirect.github.com/oxc-project/oxc/commit/b538e3c) formatter_css: Parse embedded CSS as a block's contents ([#&#8203;27284](https://redirect.github.com/oxc-project/oxc/issues/27284)) (leaysgur)

##### 🐛 Bug Fixes

- [`71e400b`](https://redirect.github.com/oxc-project/oxc/commit/71e400b) formatter_markdown: Fixed remaining issues found by fuzz ([#&#8203;27334](https://redirect.github.com/oxc-project/oxc/issues/27334)) (leaysgur)
- [`7d0e54d`](https://redirect.github.com/oxc-project/oxc/commit/7d0e54d) formatter_markdown: Preserve line breaks around Chinese/Japanese characters in all `proseWrap` ([#&#8203;27331](https://redirect.github.com/oxc-project/oxc/issues/27331)) (leaysgur)
- [`e9ae2d8`](https://redirect.github.com/oxc-project/oxc/commit/e9ae2d8) formatter_css: Fix layouts found in mdn-content repo (css-in-md) ([#&#8203;27326](https://redirect.github.com/oxc-project/oxc/issues/27326)) (leaysgur)
- [`df85b4c`](https://redirect.github.com/oxc-project/oxc/commit/df85b4c) oxfmt: Keep blank lines after a line break in Prettier Doc to IR ([#&#8203;27325](https://redirect.github.com/oxc-project/oxc/issues/27325)) (leaysgur)
- [`7f65b75`](https://redirect.github.com/oxc-project/oxc/commit/7f65b75) formatter: Format assignment target property as assignment-like ([#&#8203;27289](https://redirect.github.com/oxc-project/oxc/issues/27289)) (leaysgur)
- [`6c08f2a`](https://redirect.github.com/oxc-project/oxc/commit/6c08f2a) formatter_css: Apply the `url()` import exception to a lone comma group ([#&#8203;27288](https://redirect.github.com/oxc-project/oxc/issues/27288)) (leaysgur)
- [`1967302`](https://redirect.github.com/oxc-project/oxc/commit/1967302) formatter_css: Hug a spaced `ident (` only at the at-rule prelude head ([#&#8203;27283](https://redirect.github.com/oxc-project/oxc/issues/27283)) (leaysgur)
- [`2445064`](https://redirect.github.com/oxc-project/oxc/commit/2445064) formatter_markdown: Keep a preserved line break before a delimiter row that opens no table ([#&#8203;27278](https://redirect.github.com/oxc-project/oxc/issues/27278)) (leaysgur)
- [`dd72fbf`](https://redirect.github.com/oxc-project/oxc/commit/dd72fbf) formatter_markdown: Keep container columns as spaces under `useTabs` ([#&#8203;27277](https://redirect.github.com/oxc-project/oxc/issues/27277)) (leaysgur)
- [`1686018`](https://redirect.github.com/oxc-project/oxc/commit/1686018) oxfmt: Make one state `conditionalGroup` fit up to first `hardline` ([#&#8203;27275](https://redirect.github.com/oxc-project/oxc/issues/27275)) (leaysgur)
- [`fd4ddce`](https://redirect.github.com/oxc-project/oxc/commit/fd4ddce) formatter_json: Flatten block comment only array|object ([#&#8203;27274](https://redirect.github.com/oxc-project/oxc/issues/27274)) (leaysgur)
- [`267557c`](https://redirect.github.com/oxc-project/oxc/commit/267557c) formatter,oxfmt: Embed only original JSDoc plugin supported languages ([#&#8203;27241](https://redirect.github.com/oxc-project/oxc/issues/27241)) (leaysgur)
- [`2606c60`](https://redirect.github.com/oxc-project/oxc/commit/2606c60) oxfmt: Do not re-include a file below an excluded directory with a negated pattern ([#&#8203;27237](https://redirect.github.com/oxc-project/oxc/issues/27237)) (Nicolas Le Cam)
- [`c2de02b`](https://redirect.github.com/oxc-project/oxc/commit/c2de02b) formatter: Decide embedded template layout from AST, not source shape ([#&#8203;27218](https://redirect.github.com/oxc-project/oxc/issues/27218)) (leaysgur)
- [`c9e1224`](https://redirect.github.com/oxc-project/oxc/commit/c9e1224) formatter: Handle `quoteProps: consistent` for patterns and computed keys ([#&#8203;27216](https://redirect.github.com/oxc-project/oxc/issues/27216)) (leaysgur)
- [`36e14df`](https://redirect.github.com/oxc-project/oxc/commit/36e14df) formatter: Keep comments between callee and its opener on the callee side ([#&#8203;27172](https://redirect.github.com/oxc-project/oxc/issues/27172)) (leaysgur)
- [`6da7657`](https://redirect.github.com/oxc-project/oxc/commit/6da7657) oxfmt: Render diagnostics with source in Stdin mode ([#&#8203;27130](https://redirect.github.com/oxc-project/oxc/issues/27130)) (leaysgur)
- [`e9b2ec5`](https://redirect.github.com/oxc-project/oxc/commit/e9b2ec5) oxfmt/lsp: Reload `.prettierignore` on watched file change ([#&#8203;27129](https://redirect.github.com/oxc-project/oxc/issues/27129)) (leaysgur)
- [`30eb463`](https://redirect.github.com/oxc-project/oxc/commit/30eb463) oxfmt: Check global ignores before resolving nested config in Stdin mode ([#&#8203;27128](https://redirect.github.com/oxc-project/oxc/issues/27128)) (leaysgur)

##### ⚡ Performance

- [`72f42a3`](https://redirect.github.com/oxc-project/oxc/commit/72f42a3) formatter_markdown: Pre-allocate the IR buffer ([#&#8203;27333](https://redirect.github.com/oxc-project/oxc/issues/27333)) (leaysgur)
- [`cea47e3`](https://redirect.github.com/oxc-project/oxc/commit/cea47e3) formatter_core: Avoid exponential `will_break` check on nested interned ([#&#8203;27211](https://redirect.github.com/oxc-project/oxc/issues/27211)) (leaysgur)
- [`0aba566`](https://redirect.github.com/oxc-project/oxc/commit/0aba566) oxfmt: Do not resolve root js config from nested context ([#&#8203;27147](https://redirect.github.com/oxc-project/oxc/issues/27147)) (leaysgur)

##### 📚 Documentation

- [`51506c6`](https://redirect.github.com/oxc-project/oxc/commit/51506c6) formatter_css: Record divergences found in mdn-content (css-in-md) ([#&#8203;27327](https://redirect.github.com/oxc-project/oxc/issues/27327)) (leaysgur)
- [`656b81c`](https://redirect.github.com/oxc-project/oxc/commit/656b81c) formatter_markdown: Record unclosed fences closing at a directive's closer ([#&#8203;27279](https://redirect.github.com/oxc-project/oxc/issues/27279)) (leaysgur)
- [`66545bf`](https://redirect.github.com/oxc-project/oxc/commit/66545bf) formatter_markdown: Record setext heading wrap divergence ([#&#8203;27221](https://redirect.github.com/oxc-project/oxc/issues/27221)) (leaysgur)
- [`af4b269`](https://redirect.github.com/oxc-project/oxc/commit/af4b269) oxfmt: Document why `format()` API does not take cwd ([#&#8203;27131](https://redirect.github.com/oxc-project/oxc/issues/27131)) (leaysgur)

### [`v0.71.0`](https://redirect.github.com/oxc-project/oxc/releases/tag/oxfmt_v0.71.0): oxfmt v0.71.0

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.70.0...oxfmt_v0.71.0)

##### 🚀 Features

- [`e0b1f9f`](https://redirect.github.com/oxc-project/oxc/commit/e0b1f9f) oxfmt: Bump bundled Prettier version to 3.9.9 ([#&#8203;27002](https://redirect.github.com/oxc-project/oxc/issues/27002)) (leaysgur)
- [`342527d`](https://redirect.github.com/oxc-project/oxc/commit/342527d) oxfmt: Bump bundled Prettier version to 3.9.8 ([#&#8203;26999](https://redirect.github.com/oxc-project/oxc/issues/26999)) (leaysgur)

##### 🐛 Bug Fixes

- [`eeba1db`](https://redirect.github.com/oxc-project/oxc/commit/eeba1db) formatter: Skip test-call layout when arguments have comments ([#&#8203;27119](https://redirect.github.com/oxc-project/oxc/issues/27119)) (leaysgur)
- [`1f7b8ad`](https://redirect.github.com/oxc-project/oxc/commit/1f7b8ad) oxfmt: Allow repeated CLI calls in the same process ([#&#8203;27051](https://redirect.github.com/oxc-project/oxc/issues/27051)) (Liang)
- [`7bcb807`](https://redirect.github.com/oxc-project/oxc/commit/7bcb807) formatter_markdown: Keep blank line between HTML and nested list ([#&#8203;27112](https://redirect.github.com/oxc-project/oxc/issues/27112)) (leaysgur)
- [`10b10c5`](https://redirect.github.com/oxc-project/oxc/commit/10b10c5) formatter: Keep comments around `=` on their side and line ([#&#8203;27041](https://redirect.github.com/oxc-project/oxc/issues/27041)) (leaysgur)
- [`9aad365`](https://redirect.github.com/oxc-project/oxc/commit/9aad365) formatter: Keep comments deferred before an assignment operator ([#&#8203;26997](https://redirect.github.com/oxc-project/oxc/issues/26997)) (waltu)
- [`8fbddb1`](https://redirect.github.com/oxc-project/oxc/commit/8fbddb1) formatter/jsdoc: More alignment with original plugin ([#&#8203;27039](https://redirect.github.com/oxc-project/oxc/issues/27039)) (leaysgur)
- [`50be18e`](https://redirect.github.com/oxc-project/oxc/commit/50be18e) formatter: Keep trailing spaces on normal block comments ([#&#8203;27037](https://redirect.github.com/oxc-project/oxc/issues/27037)) (leaysgur)
- [`56d1880`](https://redirect.github.com/oxc-project/oxc/commit/56d1880) formatter: Nestle adjacent block comments ([#&#8203;27036](https://redirect.github.com/oxc-project/oxc/issues/27036)) (leaysgur)
- [`3be5d94`](https://redirect.github.com/oxc-project/oxc/commit/3be5d94) formatter: Treat `/***` comments as JSDoc ([#&#8203;27035](https://redirect.github.com/oxc-project/oxc/issues/27035)) (leaysgur)
- [`cd45f71`](https://redirect.github.com/oxc-project/oxc/commit/cd45f71) formatter: Keep trailing double spaces on JSDoc lines ([#&#8203;26861](https://redirect.github.com/oxc-project/oxc/issues/26861)) (John Costa)
- [`fd695f4`](https://redirect.github.com/oxc-project/oxc/commit/fd695f4) formatter_markdown: Fix more mismatches found in ecosystem-ci repos ([#&#8203;27003](https://redirect.github.com/oxc-project/oxc/issues/27003)) (leaysgur)
- [`4e77d59`](https://redirect.github.com/oxc-project/oxc/commit/4e77d59) formatter_markdown: Keep a math span after a kept line break from opening a block ([#&#8203;27001](https://redirect.github.com/oxc-project/oxc/issues/27001)) (leaysgur)
- [`584b8b0`](https://redirect.github.com/oxc-project/oxc/commit/584b8b0) formatter_markdown: Keep a shape line after a multi-line inline node or link title ([#&#8203;27000](https://redirect.github.com/oxc-project/oxc/issues/27000)) (leaysgur)
- [`b939645`](https://redirect.github.com/oxc-project/oxc/commit/b939645) formatter_markdown: Keep a line break before an inline liquid tag under preserve ([#&#8203;26998](https://redirect.github.com/oxc-project/oxc/issues/26998)) (leaysgur)

### [`v0.70.0`](https://redirect.github.com/oxc-project/oxc/releases/tag/oxfmt_v0.70.0): oxfmt v0.70.0

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.69.0...oxfmt_v0.70.0)

##### 🚀 Features

- [`415b742`](https://redirect.github.com/oxc-project/oxc/commit/415b742) oxlint,oxfmt: Do not discover nested config in Vite+ mode ([#&#8203;26763](https://redirect.github.com/oxc-project/oxc/issues/26763)) (leaysgur)

### [`v0.69.0`](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.68.0...oxfmt_v0.69.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.68.0...oxfmt_v0.69.0)

### [`v0.68.0`](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.67.0...oxfmt_v0.68.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.67.0...oxfmt_v0.68.0)

</details>

<details>
<summary>oxc-project/oxc (oxlint)</summary>

### [`v1.88.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1880---2026-10-13)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.87.0...oxlint_v1.88.0)

##### 🚀 Features

- [`3c361a7`](https://redirect.github.com/oxc-project/oxc/commit/3c361a7) linter/unicorn/prefer-query-selector: Add `allowWithVariables` option ([#&#8203;27346](https://redirect.github.com/oxc-project/oxc/issues/27346)) (Mikhail Baev)

### [`v1.87.0`](https://redirect.github.com/oxc-project/oxc/releases/tag/oxlint_v1.87.0): oxlint v1.87.0

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.86.0...oxlint_v1.87.0)

##### 🚀 Features

- [`42dcfd2`](https://redirect.github.com/oxc-project/oxc/commit/42dcfd2) linter/react/no-unescaped-entities: Implement suggestion ([#&#8203;27292](https://redirect.github.com/oxc-project/oxc/issues/27292)) (Mikhail Baev)
- [`cd4510d`](https://redirect.github.com/oxc-project/oxc/commit/cd4510d) linter/unicorn/no-useless-switch-case: Implement suggestion ([#&#8203;27245](https://redirect.github.com/oxc-project/oxc/issues/27245)) (Mikhail Baev)
- [`1a7c902`](https://redirect.github.com/oxc-project/oxc/commit/1a7c902) linter/react/jsx-no-target-blank: Implement suggestion ([#&#8203;27181](https://redirect.github.com/oxc-project/oxc/issues/27181)) (Mikhail Baev)

##### 🐛 Bug Fixes

- [`176b4b9`](https://redirect.github.com/oxc-project/oxc/commit/176b4b9) linter/jsx-a11y/label-has-associated-control: Validate label attribute values ([#&#8203;27302](https://redirect.github.com/oxc-project/oxc/issues/27302)) (sama Pyb)
- [`7d381c3`](https://redirect.github.com/oxc-project/oxc/commit/7d381c3) linter/jsx-a11y/mouse-events-have-key-events: Handle nullish event handlers ([#&#8203;27306](https://redirect.github.com/oxc-project/oxc/issues/27306)) (sama Pyb)
- [`4e09837`](https://redirect.github.com/oxc-project/oxc/commit/4e09837) linter/jsx-a11y/lang: Validate lang expression strings ([#&#8203;27304](https://redirect.github.com/oxc-project/oxc/issues/27304)) (sama Pyb)
- [`7d28a66`](https://redirect.github.com/oxc-project/oxc/commit/7d28a66) linter/unicorn/numeric-separators-style: Report misgrouped BigInt literals ([#&#8203;27207](https://redirect.github.com/oxc-project/oxc/issues/27207)) (breken)
- [`e928322`](https://redirect.github.com/oxc-project/oxc/commit/e928322) linter/unicorn/no-zero-fractions: Parenthesize separator and large integers in fixer ([#&#8203;27206](https://redirect.github.com/oxc-project/oxc/issues/27206)) (breken)
- [`3d61a59`](https://redirect.github.com/oxc-project/oxc/commit/3d61a59) parser: Validate TypeScript type member separators ([#&#8203;27222](https://redirect.github.com/oxc-project/oxc/issues/27222)) (camc314)
- [`f08236b`](https://redirect.github.com/oxc-project/oxc/commit/f08236b) linter/eslint/prefer-exponentiation-operator: Preserve autofix precedence ([#&#8203;27150](https://redirect.github.com/oxc-project/oxc/issues/27150)) (camc314)
- [`7d60ae3`](https://redirect.github.com/oxc-project/oxc/commit/7d60ae3) linter/valid-title: Continue checks after allowed words ([#&#8203;27146](https://redirect.github.com/oxc-project/oxc/issues/27146)) (Cameron)
- [`ad5dd5d`](https://redirect.github.com/oxc-project/oxc/commit/ad5dd5d) linter/eslint/no-unused-vars: Respect disabled argument checks for used ignore patterns ([#&#8203;26925](https://redirect.github.com/oxc-project/oxc/issues/26925)) (camc314)

### [`v1.86.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1860---2026-09-28)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.85.0...oxlint_v1.86.0)

##### 🚀 Features

- [`9d80eed`](https://redirect.github.com/oxc-project/oxc/commit/9d80eed) linter/react/only-export-components: Support `allowCompoundComponents` ([#&#8203;27117](https://redirect.github.com/oxc-project/oxc/issues/27117)) (Kuroda Kayn)
- [`e05b155`](https://redirect.github.com/oxc-project/oxc/commit/e05b155) linter: Add typescript/no-generated-empty-object-type ([#&#8203;26958](https://redirect.github.com/oxc-project/oxc/issues/26958)) (camc314)

### [`v1.85.0`](https://redirect.github.com/oxc-project/oxc/releases/tag/oxlint_v1.85.0): oxlint v1.85.0

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.84.0...oxlint_v1.85.0)

##### 🚀 Features

- [`415b742`](https://redirect.github.com/oxc-project/oxc/commit/415b742) oxlint,oxfmt: Do not discover nested config in Vite+ mode ([#&#8203;26763](https://redirect.github.com/oxc-project/oxc/issues/26763)) (leaysgur)

### [`v1.84.0`](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.83.0...oxlint_v1.84.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.83.0...oxlint_v1.84.0)

### [`v1.83.0`](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.82.0...oxlint_v1.83.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.82.0...oxlint_v1.83.0)

</details>

<details>
<summary>prettier/prettier (prettier)</summary>

### [`v3.9.10`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#3910)

[Compare Source](https://redirect.github.com/prettier/prettier/compare/3.9.9...3.9.10)

[diff](https://redirect.github.com/prettier/prettier/compare/3.9.9...3.9.10)

##### Markdown: Update `micromark-extension-gfm-table` to fix the table parse issue on performance ([#&#8203;20266](https://redirect.github.com/prettier/prettier/pull/20266) by [@&#8203;fisker](https://redirect.github.com/fisker))

See [micromark/micromark-extension-gfm-table@`4f43ead`](https://redirect.github.com/micromark/micromark-extension-gfm-table/commit/4f43ead37b786bc531d924530d4ab4c714886267) for details.

### [`v3.9.9`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#399)

[Compare Source](https://redirect.github.com/prettier/prettier/compare/3.9.8...3.9.9)

[diff](https://redirect.github.com/prettier/prettier/compare/3.9.8...3.9.9)

##### Markdown: Fix text with `$` been incorrectly parsed as math syntax ([#&#8203;20140](https://redirect.github.com/prettier/prettier/pull/20140) by [@&#8203;fisker](https://redirect.github.com/fisker))

<!-- prettier-ignore -->

```md
<!-- Input -->
**Uses $FOO** from `a.sh` and `b.sh`, plus `$BAR` from `c.sh`, before anything else runs here.

<!-- Prettier 3.9.8 -->
**Uses $FOO** from `a.sh` and `b.sh`, plus `$BAR`from`c.sh`, before anything else runs here.

<!-- Prettier 3.9.9 -->
**Uses $FOO** from `a.sh` and `b.sh`, plus `$BAR` from `c.sh`, before anything else runs here.
```

### [`v3.9.8`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#398)

[Compare Source](https://redirect.github.com/prettier/prettier/compare/3.9.7...3.9.8)

[diff](https://redirect.github.com/prettier/prettier/compare/3.9.7...3.9.8)

##### Markdown: Don't let Liquid objects interrupt paragraphs ([#&#8203;20087](https://redirect.github.com/prettier/prettier/pull/20087) by [@&#8203;seiyab](https://redirect.github.com/seiyab))

<!-- prettier-ignore -->

```markdown
<!-- Input -->
If `module` is not a [`WebAssembly.Module`](/en-US/docs/WebAssembly/Reference/JavaScript_interface/Module) object instance, a
{{jsxref("TypeError")}} is thrown.

<!-- Prettier 3.9.7 -->
If `module` is not a [`WebAssembly.Module`](/en-US/docs/WebAssembly/Reference/JavaScript_interface/Module) object instance, a

{{jsxref("TypeError")}} is thrown.

<!-- Prettier 3.9.8 -->
If `module` is not a [`WebAssembly.Module`](/en-US/docs/WebAssembly/Reference/JavaScript_interface/Module) object instance, a
{{jsxref("TypeError")}} is thrown.
```

### [`v3.9.7`](https://redirect.github.com/prettier/prettier/blob/HEAD/CHANGELOG.md#397)

[Compare Source](https://redirect.github.com/prettier/prettier/compare/3.9.6...3.9.7)

[diff](https://redirect.github.com/prettier/prettier/compare/3.9.6...3.9.7)

##### Markdown: Prevent indentation drift in list-item code blocks ([#&#8203;19647](https://redirect.github.com/prettier/prettier/pull/19647), [#&#8203;19990](https://redirect.github.com/prettier/prettier/pull/19990) by [@&#8203;Austin1serb](https://redirect.github.com/Austin1serb), [@&#8203;giaBaoJS](https://redirect.github.com/giaBaoJS))

<!-- prettier-ignore -->

```markdown
<!-- Input -->
- [x] short first line.

      second paragraph at six spaces that wraps
      onto another line here.

<!-- Prettier 3.9.6 -->
- [x] short first line.

      second paragraph at six spaces that wraps
          onto another line here.

<!-- Prettier 3.9.7 -->
- [x] short first line.

      second paragraph at six spaces that wraps
      onto another line here.
```

##### JavaScript: Fix embedded template literal idempotency ([#&#8203;19725](https://redirect.github.com/prettier/prettier/pull/19725) by [@&#8203;fisker](https://redirect.github.com/fisker))

<!-- prettier-ignore -->

```jsx
// Input
const foo = html`
            <div>${getText({
            value,
            })}</div>
`;

// Prettier 3.9.6 (first format)
const foo = html`
  <div>
    ${getText({
              value,
            })}
  </div>
`;

// Prettier 3.9.6 (second format)
const foo = html`
  <div>
    ${getText({
      value,
    })}
  </div>
`;

// Prettier 3.9.7
const foo = html`
  <div>
    ${getText({
      value,
    })}
  </div>
`;
```

##### Markdown: Preserve Liquid blocks after Markdown tables ([#&#8203;19730](https://redirect.github.com/prettier/prettier/pull/19730) by [@&#8203;wanxiankai](https://redirect.github.com/wanxiankai), [@&#8203;seiyab](https://redirect.github.com/seiyab))

<!-- prettier-ignore -->

```markdown
<!-- Input -->
| Argument | Type |
| -------- | ---- |
{% set default_params = {
    "model": ["str"],
} %}

<!-- Prettier 3.9.6 -->
| Argument                  | Type |
| ------------------------- | ---- |
| {% set default_params = { |

    "model": ["str"],

} %}

<!-- Prettier 3.9.7 -->
| Argument | Type |
| -------- | ---- |
{% set default_params = {
    "model": ["str"],
} %}
```

##### Markdown: Preserve single tildes in Markdown ([#&#8203;19739](https://redirect.github.com/prettier/prettier/pull/19739) by [@&#8203;lazerg](https://redirect.github.com/lazerg))

Since 3.9.0, single-tilde spans like `H~2~O` were rewritten to double tildes, turning subscript syntax into strike-through. Only double tildes are treated that way now, matching GitHub.

<!-- prettier-ignore -->

```markdown
<!-- Input -->
H~2~O

<!-- Prettier 3.9.6 -->
H~~2~~O

<!-- Prettier 3.9.7 -->
H~2~O
```

##### Markdown: Fix Markdown blockquote containing `>` characters ([#&#8203;19802](https://redirect.github.com/prettier/prettier/pull/19802) by [@&#8203;seiyab](https://redirect.github.com/seiyab))

<!-- prettier-ignore -->

```md
<!-- Input -->
> **test**\>

<!-- Prettier 3.9.6 -->
> **test**>\>

<!-- Prettier 3.9.7 -->
> **test**\>
```

##### Markdown: Strip blockquote markers from a setext heading's continuation lines ([#&#8203;19878](https://redirect.github.com/prettier/prettier/pull/19878) by [@&#8203;Kjubikstronk](https://redirect.github.com/Kjubikstronk))

A setext heading spanning multiple lines inside a blockquote kept the `>` marker of its continuation lines as literal text.

<!-- prettier-ignore -->

```md
<!-- Input -->
> Multi
> Line
> ===

<!-- Prettier 3.9.6 -->
> Multi > Line
> ===

<!-- Prettier 3.9.7 -->
> Multi
> Line
> ===
```

##### Markdown: Fix HTML block indentation drift in markdown list items ([#&#8203;19987](https://redirect.github.com/prettier/prettier/pull/19987) by [@&#8203;lazerg](https://redirect.github.com/lazerg))

<!-- prettier-ignore -->

```markdown
<!-- Input -->
1. Enable images:

    <p align="center">
      <img alt="example">
    </p>

<!-- Prettier 3.9.6 (--tab-width=4) -->
1. Enable images:

     <p align="center">
       <img alt="example">
     </p>

<!-- Prettier 3.9.7 (--tab-width=4) -->
1. Enable images:

    <p align="center">
      <img alt="example">
    </p>
```

##### Angular: Support `@boundary` error boundaries ([#&#8203;20014](https://redirect.github.com/prettier/prettier/pull/20014) by [@&#8203;Yahiro025](https://redirect.github.com/Yahiro025))

<!-- prettier-ignore -->

```html
<!-- Input -->
@boundary {
<complex-chart [data]="data"/>
}
@error (let err) {
<p>{{err.message}}</p>
}

<!-- Prettier 3.9.6 -->
SyntaxError: Unexpected character "EOF" (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.) (6:1)

<!-- Prettier 3.9.7 -->
@boundary {
  <complex-chart [data]="data" />
} @error (let err) {
  <p>{{ err.message }}</p>
}
```

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:26:37 -07:00
renovate[bot] 0285cc8607 chore(deps): update dependency uuid to v14.0.3 (#724)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [uuid](https://redirect.github.com/uuidjs/uuid) | [`14.0.2` → `14.0.3`](https://renovatebot.com/diffs/npm/uuid/14.0.2/14.0.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/uuid/14.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/uuid/14.0.2/14.0.3?slim=true) |

---

### Release Notes

<details>
<summary>uuidjs/uuid (uuid)</summary>

### [`v14.0.3`](https://redirect.github.com/uuidjs/uuid/blob/HEAD/CHANGELOG.md#1403-2026-10-09)

[Compare Source](https://redirect.github.com/uuidjs/uuid/compare/v14.0.2...v14.0.3)

##### Bug Fixes

- fix CITGM [#&#8203;1154](https://redirect.github.com/uuidjs/uuid/issues/1154) ([#&#8203;981](https://redirect.github.com/uuidjs/uuid/issues/981)) ([b44f159](https://redirect.github.com/uuidjs/uuid/commit/b44f159d5a4af4eb7a144ab35dcabcfd58ef0aac))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:26:24 -07:00
renovate[bot] e5bd655b6b chore(deps): update dependency @types/node to v24.19.2 (#722)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`24.19.1` → `24.19.2`](https://renovatebot.com/diffs/npm/@types%2fnode/24.19.1/24.19.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/24.19.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/24.19.1/24.19.2?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNjQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-19 11:25:25 -07:00
renovate[bot] ab24672e58 chore(deps): update dependency jdx/mise to v2026.10.8 (#720)
> [!NOTE]
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jdx/mise](https://redirect.github.com/jdx/mise) | minor | `v2026.9.7` → `v2026.10.8` |

---

### Release Notes

<details>
<summary>jdx/mise</summary>

### [`v2026.10.8`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.8): : Stable `global` install link for IDEs

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.7...vfox-v2026.10.8)

Each tool's install directory now has a `global` link that follows your global config, which gives IDEs a path that doesn't change when you switch versions. This release also retries downloads whose response body drops partway through, fixes lockfile checks for release tags that contain `@`, and stops dotfiles history from rewriting files that haven't changed.

##### Added

- **`global` runtime symlink.** Each tool's install directory now has a `global` link to the version your global and system config select. Programs that can't run mise, such as IDEs, can point at this path and follow `mise use -g` changes. [#&#8203;14271](https://redirect.github.com/jdx/mise/pull/14271)

  ```text
  ~/.local/share/mise/installs/maven/
  ├── 3.9.13/
  ├── 3.9.16/
  └── global -> ./3.9.16
  ```

  The link is updated along with the other runtime symlinks (`latest`, `3`, ...), for example after `mise use -g`, `mise install` and `mise upgrade`. Project config and `MISE_*_VERSION` don't change it. If no global config selects the tool, mise removes the link. A real directory named `global` is left alone. The link is always on, and no setting turns it off.

##### Fixed

- HTTP requests now retry, up to `http_retries`, when the response body drops partway through. Before, only the request and headers were retried, so a version list or release page cut off mid-transfer failed with `error decoding response body`. This applies to text, JSON and byte requests, and to version lists read with `version_regex`. [#&#8203;14253](https://redirect.github.com/jdx/mise/pull/14253) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- `mise install` no longer rejects lockfile entries whose release tag contains `@`, such as `[email protected].0`. `mise lock` writes these tags percent-encoded (`cli%400.46.0`), and the locked-URL check read that as version `400.46.0`. Re-running `mise lock` couldn't fix it. [#&#8203;14252](https://redirect.github.com/jdx/mise/pull/14252)
- Dotfiles history index rebuilds now skip checkpoint metadata files whose contents haven't changed. On large histories, rebuilds used to rewrite every file, which caused minutes of disk writes and made `mise dot save` fail with `another history operation is running`. [#&#8203;14249](https://redirect.github.com/jdx/mise/pull/14249)
- The JSON schema now accepts `outputs = { auto = false }` on tasks, so editors and linters such as tombi and Taplo no longer flag it. mise's behavior is unchanged: `auto = false` means the same as leaving `outputs` unset. [#&#8203;14265](https://redirect.github.com/jdx/mise/pull/14265) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

##### Documentation

- The dotfiles docs now open with a recommended setup, and the modes-and-keys reference table has been reorganized into three readable columns. [#&#8203;14267](https://redirect.github.com/jdx/mise/pull/14267)

##### Registry

- New aqua packages: [`runxhq/runx`](https://redirect.github.com/runxhq/runx) and [`takumin/passt-bin`](https://redirect.github.com/takumin/passt-bin).

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.7...v2026.10.8>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.7`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.7): : Background tool updates at the shell prompt, a verified download cache, and `mise upgrade --global`

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.6...vfox-v2026.10.7)

Tools with `auto_update` now update in the background from an activated shell, and a new `tool_update.global_auto` setting turns this on for every global tool. mise also keeps finished downloads in a content-addressed cache and checks them again before reusing them. This release also fixes `.env` parsing regressions from 2026.10.3 and a crash in `mise run`.

##### Added

- **Automatic updates at the shell prompt.** Before, `auto_update` only ran when a shim or `mise exec` launched a tool. With `mise activate`, tools run straight from `PATH`, so they were never updated. Now, when a check is due, `mise hook-env` starts the update in a detached background process. The prompt doesn't wait and nothing is printed. The next command after the update finishes uses the new version. The existing rules still apply: only global config counts, exact versions don't move, nothing updates offline, in CI or with `locked`, and `minimum_release_age` is honored. While the `tool-update` service is running, prompts leave updates to it. Failed background updates show up in `mise doctor`. [#&#8203;14239](https://redirect.github.com/jdx/mise/pull/14239)

- **`tool_update.global_auto`** turns on automatic updates for every tool in global config at once. It takes the same values as `auto_update`: `true` checks every `tool_update.check_duration` (24h by default), and a duration sets the interval. A tool's own `auto_update` takes precedence, so `auto_update = false` opts a tool out. Projects can't enable this setting. You can also set it with `MISE_TOOL_UPDATE_GLOBAL_AUTO`. `self_update.auto` now accepts an interval too, for example `self_update.auto = "1d"`. [#&#8203;14237](https://redirect.github.com/jdx/mise/pull/14237)

  ```toml
  # ~/.config/mise/config.toml
  [settings]
  tool_update.global_auto = "12h"

  [tools]
  claude = "latest"                                   # checked every 12h
  node = { version = "24", auto_update = "6h" }       # its own interval wins
  python = { version = "3.13", auto_update = false }  # opted out
  ```

- **`mise upgrade --global` and `mise outdated --global`** act only on the tool requests in global and system config, even inside a project that pins its own version or when `MISE_<TOOL>_VERSION` is set. The project's `mise.toml`, lockfile and `[env]` are not read or changed. `--bump` writes to the global config. `--global` can't be combined with `--local` or `--inactive`. A new "Self-updating tools" docs page explains how tools that update themselves, such as coding agents, can detect a mise install and update through mise instead of overwriting their own install. [#&#8203;14243](https://redirect.github.com/jdx/mise/pull/14243)

  ```sh
  mise outdated --global claude --json
  mise upgrade --global claude
  ```

- **Verified download cache.** Finished downloads are stored once in `$MISE_CACHE_DIR/downloads-cas` and hashed again before every reuse. A cached file that no longer matches is dropped and downloaded again. [#&#8203;14222](https://redirect.github.com/jdx/mise/pull/14222), [#&#8203;14242](https://redirect.github.com/jdx/mise/pull/14242)
  - When the server sent an `ETag` or `Last-Modified` header, the next request for the same URL is conditional. On `304 Not Modified`, mise reuses the cached file without downloading it again. This applies to every download through mise's HTTP client.
  - For tools with a pinned checksum (from tool options, `mise.lock` or a packslip digest), `http`, `github`, `aqua`, `packslip` and the precompiled core tools (Node, Go, Bun, Deno, Zig, Java, Python, Ruby, Erlang, Elixir, Swift) reuse the cached file without any network request. The file is hard-linked into place, so it doesn't use extra disk space.
  - With `--locked` or `paranoid`, mise only uses the cache for downloads with a pinned checksum. This means a CI cache you restore can save downloads but can't change what gets installed. The CI guide now shows how to cache `~/.cache/mise/downloads-cas` instead of installed tools.
  - `download_cache_max_size` sets the size limit (default `2GiB`, `0` for no limit). When the cache is full, the least recently used files are removed. `download_cache = false` (`MISE_DOWNLOAD_CACHE=0`) turns the cache off.

- **`npm:` packages that install no executables now trigger a warning.** For example, `mise use npm:lodash` used to succeed silently even though it created no shims. The install still succeeds. [#&#8203;14246](https://redirect.github.com/jdx/mise/pull/14246)

##### Fixed

- **`.env` parsing regressions from 2026.10.3**, affecting `[env] _.file` and the `env_file` setting:
  - Backslashes in single-quoted values are literal again, so Windows paths like `'\\fileserver\share'` and `'C:\temp\'` load correctly. To embed a single quote, use double quotes. [#&#8203;14228](https://redirect.github.com/jdx/mise/pull/14228)
  - Quoted and unquoted parts placed next to each other are joined again, as in shell. For example, `A='it'\''s'` gives `it's` and `B=a'b c'd` gives `ab cd`. These lines used to cause syntax errors that dropped the rest of the file. Quotes inside an unquoted value are now removed, so JSON that needs to keep its quotes must be single-quoted (`CONFIG='{"debug": true}'`). A quote with no matching closing quote on the same line stays literal, so `NAME=O'Brien` reads as written. [#&#8203;14231](https://redirect.github.com/jdx/mise/pull/14231)
- `mise run` no longer crashes now and then with `called Option::unwrap() on a None value` when it reads the tool list while it's being reloaded after an install. [#&#8203;14240](https://redirect.github.com/jdx/mise/pull/14240)
- On Windows with `core.autocrlf=true`, `mise bootstrap repos status` no longer reports freshly cloned repos as dirty. [#&#8203;14225](https://redirect.github.com/jdx/mise/pull/14225)
- On Windows, `mise dot apply` can now repoint a `mode = "symlink"` dotfile whose target is a directory junction after its `source` changes. It used to fail with `os error 5`. [#&#8203;14226](https://redirect.github.com/jdx/mise/pull/14226)
- `history.describe_command` now works for files tracked through a variant (such as `home@work/...`), including files added or removed inside variant directories. Encrypted files are still left out of the diff. [#&#8203;14194](https://redirect.github.com/jdx/mise/pull/14194) ([@&#8203;oppegard](https://redirect.github.com/oppegard))
- `mise doctor` now shows the `dotfiles:` section, including the history `repo:` and `origin:`, when `history.enabled = false`. `mise doctor --json` adds `dotfiles.history_enabled` and `dotfiles.tracking_error`. [#&#8203;14244](https://redirect.github.com/jdx/mise/pull/14244)
- `mise lock` now applies aqua registry version prefixes (such as Codex's `rust-` prefix) before it chooses version overrides. Before, platform entries like `windows-x64` were skipped. When several prefix families match, mise now warns and skips the entry instead of picking one arbitrarily. [#&#8203;14218](https://redirect.github.com/jdx/mise/pull/14218) ([@&#8203;nettlesh](https://redirect.github.com/nettlesh))
- The rustup profile aliases `m`, `d`, `c` and the empty value are now treated like `minimal`, `default` and `complete`. With `profile = "d"`, `mise install` now restores missing components like clippy and rustfmt. Unknown profile names now fail with rustup's list of valid names. [#&#8203;14220](https://redirect.github.com/jdx/mise/pull/14220) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- The cargo backend now takes the same rust-state lock as the rust plugin when `CARGO_HOME`/`RUSTUP_HOME` are set through the `rust.cargo_home`/`rust.rustup_home` settings or `MISE_CARGO_HOME`/`MISE_RUSTUP_HOME` in `[env]`. Before, parallel source-build installs could race and fail. [#&#8203;14219](https://redirect.github.com/jdx/mise/pull/14219) ([@&#8203;wislertt](https://redirect.github.com/wislertt))
- The hint to enable `self_update.auto` now appears on every `mise version` and `mise self-update` run, not just the first time. It stops once you enable auto-update or run `mise settings add disable_hints auto_update`. [#&#8203;14233](https://redirect.github.com/jdx/mise/pull/14233)
- The JSON schema now matches what mise accepts, so editors and linters like tombi and Taplo report the right errors ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562)):
  - Task arrays can contain `{ task = ... }` and `{ tasks = [...] }` entries. [#&#8203;14229](https://redirect.github.com/jdx/mise/pull/14229)
  - `depends`, `depends_post` and `wait_for` accept a single table, and empty nested lists like `depends = [[]]` are rejected. [#&#8203;14236](https://redirect.github.com/jdx/mise/pull/14236)
  - `[env]` entries that combine `value` with `required = true` or a help string, and `{ required = false }` with no value, are rejected. [#&#8203;14247](https://redirect.github.com/jdx/mise/pull/14247)

##### Deprecated

- `always_keep_download` is deprecated because downloads are now kept in the download cache. It will warn starting in 2026.11.0 and will be removed in 2027.11.0. [#&#8203;14222](https://redirect.github.com/jdx/mise/pull/14222)

##### Documentation

- The automatic tool updates docs now explain that `minimum_release_age` (24h by default) plus the check interval can delay an update by 24 to 48 hours after a release. You can set `minimum_release_age` per tool. Docs and hints now use the shorter `mise settings KEY=VALUE` form. [#&#8203;14235](https://redirect.github.com/jdx/mise/pull/14235)

##### New Contributors

- [@&#8203;wislertt](https://redirect.github.com/wislertt) made their first contribution in [#&#8203;14219](https://redirect.github.com/jdx/mise/pull/14219)
- [@&#8203;oppegard](https://redirect.github.com/oppegard) made their first contribution in [#&#8203;14194](https://redirect.github.com/jdx/mise/pull/14194)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.6...v2026.10.7>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.6`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.6): : Per-machine [vars] prompts, local config includes, and working-tree changes in --affected

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.5...vfox-v2026.10.6)

`[vars]` entries can now ask once per machine and remember the answer, `include` accepts local files, and `mise run --affected` now counts uncommitted work and no longer needs `experimental = true`. On a fresh machine, `mise bootstrap` can install git and ssh before it clones. The release also fixes several dotfiles history issues and stops lazy tools from triggering remote version lookups when something else launches.

##### Added

- **Per-machine `[vars]` prompts.** Give a var a `prompt`, plus either a `default` or `required`. mise asks for it once and saves the answer in `$MISE_STATE_DIR/vars.toml`, outside your config and dotfiles history. Templates, tasks and tools then read it as usual. One shared dotfiles setup can use this to set a different Git identity on each machine. [#&#8203;14190](https://redirect.github.com/jdx/mise/pull/14190)

  ```toml
  [vars.git_name]
  default = "Ada Lovelace"
  prompt = "Git author name"

  [vars.git_email]
  required = "Run mise vars prompt to set git_email"
  prompt = "Git email"
  ```

  ```sh
  mise vars                       # list every var and where its value comes from
  mise vars prompt                # ask for every unanswered prompt var (or name some)
  mise vars git_name=Ada          # save a value without a prompt
  mise vars unset git_name        # forget a saved value
  mise bootstrap --prompt-vars    # prompt during a bootstrap run, e.g. with --adopt
  ```

  Only `mise vars prompt` and `mise bootstrap --prompt-vars` ever ask. Everything else uses the saved answer, then the `default`. Precedence, highest first: the process environment, a value from a higher-precedence config file, the saved answer, the `default`. Answers are keyed by var name, so projects that use the same name share one answer on a machine. `prompt` is not allowed in `[env]`.

- **Local paths in `include`.** `include` in `mise.toml` now accepts local TOML files, not just `git::` and `oci::` references. A path can be relative to the including file or absolute. Local files merge the same way as remote ones, ranking just below the including file. They are read on every load, so edits apply immediately and invalidate the cached env. Paranoid mode rejects local includes, and safe mode still skips all includes in project config. [#&#8203;14178](https://redirect.github.com/jdx/mise/pull/14178)

  ```toml
  include = ["./config/tools.toml", "/etc/mise/team.toml"]
  ```

- **`mise bootstrap` installs git and ssh before cloning.** `mise bootstrap --from` and `--adopt` used to fail with a spawn error when git was missing. Now mise checks for git, and for ssh when the URL is `ssh://` or `user@host:path`, and offers to install whatever is missing with the host package manager. Supported managers are apt, dnf, pacman, apk, zypper, Homebrew, scoop and winget. `--yes` accepts the offer without asking, and `--dry-run` only reports what is missing. [#&#8203;14188](https://redirect.github.com/jdx/mise/pull/14188)

- **`mise dot save --re-encrypt`.** After you change `[history.encryption] recipients`, a normal save leaves some encrypted files under their old keys: manual-save entries the save doesn't name, and other-platform variants. Add `--re-encrypt` to re-encrypt every saved file in the new checkpoint to the current recipients. It never captures unsaved edits. If this machine can't unlock some of the files, nothing is saved and the error lists all of them. [#&#8203;14210](https://redirect.github.com/jdx/mise/pull/14210)

- **`mise dot status` shows unsaved changes.** It now lists tracked paths that changed since the latest checkpoint. Unsaved edits were previously invisible there when the watcher was stopped or an entry used `--no-autosave`. `--json` reports these paths as `history.unsaved`. The value is `null` when they can't be determined, for example with no checkpoint yet or with encrypted files this machine can't compare without prompting. [#&#8203;14209](https://redirect.github.com/jdx/mise/pull/14209)

##### Changed

- **`mise run --affected` now counts working-tree changes, and the workspace graph is no longer experimental.** Besides the committed `base...head` range, `--affected` now also counts staged and unstaged edits and untracked files that aren't ignored. You can narrow it with `--affected-committed`, `--affected-uncommitted` and `--affected-untracked`, which can be combined (environment variables: `MISE_AFFECTED_*`). Working-tree changes only count when the head is your current checkout. To get the old committed-only selection, for example in a CI job that writes untracked files before it runs, use `--affected-committed`. `--affected`, `mise tasks graph`, `[monorepo.task_defaults]`, `[monorepo.projects]` and `task.auto_infer` no longer require `experimental = true`. [#&#8203;14214](https://redirect.github.com/jdx/mise/pull/14214)

  ```sh
  mise run --affected build                                         # committed + uncommitted + untracked
  mise run --affected --affected-uncommitted --affected-untracked build  # only what you haven't committed yet
  mise run --affected --affected-committed build                    # committed range only, as before
  ```

- **Credential checks for dotfiles.** Files with names ending in `.example`, `.sample` or `.template` no longer match the credential name rules. The content scan before publishing still checks them. Once you approve a file for plaintext tracking, mise no longer warns about it on every save. [#&#8203;14204](https://redirect.github.com/jdx/mise/pull/14204)

- **`mise dot track` only asks about plaintext at a terminal.** Before saving a credential-named file in plaintext, it now asks only when both stdin and stderr are terminals. Previously, a piped line, such as the next entry in a `while read` loop, could be taken as the answer. Scripts need `--allow-plaintext` or `--encrypt`. [#&#8203;14205](https://redirect.github.com/jdx/mise/pull/14205)

##### Fixed

- **Lazy tools no longer trigger remote lookups for other lazy tools.** Previously, mise looked up versions for configured lazy tools that weren't installed, even though they weren't being launched. With a cold cache and no network, launching one tool waited on each lookup and could print "Failed to resolve tool version list" warnings. In one offline test a launch took 41 seconds. This no longer happens in these cases:

  - the update that runs before an `auto_update` tool launches ([#&#8203;14195](https://redirect.github.com/jdx/mise/pull/14195))
  - after `mise x` or a shim installs a new version ([#&#8203;14197](https://redirect.github.com/jdx/mise/pull/14197))
  - the first launch of a lazy tool through its shim ([#&#8203;14201](https://redirect.github.com/jdx/mise/pull/14201))

  `mise install`, `mise upgrade` and `mise use` still resolve everything.
- **`Too many open files` with parallel `npm:` installs.** Parallel installs could fail under macOS's default launchd soft limit of 256, for example when mise was started from a LaunchAgent. On Unix, mise now raises its soft open-file limit at startup toward the hard limit, up to 10240, and never lowers it. [#&#8203;14174](https://redirect.github.com/jdx/mise/pull/14174), [#&#8203;14216](https://redirect.github.com/jdx/mise/pull/14216)
- **`go:` tools with an untidy `go.mod`.** Tools whose published `go.mod` is missing a requirement now install, for example `go:tailscale.com/cmd/tailscale`. mise now passes `-mod=mod` to `go install` instead of `-mod=readonly`. A project's `GOFLAGS=-mod=vendor` still doesn't affect these installs. [#&#8203;14180](https://redirect.github.com/jdx/mise/pull/14180) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **Config at the filesystem root.** A config file at `/`, such as `/mise.toml` in a Docker image, made `mise install` and `mise exec` panic. It now works. [#&#8203;14182](https://redirect.github.com/jdx/mise/pull/14182)
- **.NET installs.** Installing a .NET SDK no longer fails with "was not found in `dotnet --list-sdks` output" when another mise-managed SDK comes before the shims on `PATH`. The post-install check now runs the new SDK's `dotnet` directly. [#&#8203;14189](https://redirect.github.com/jdx/mise/pull/14189)
- **Shims and directories.** Shims and `mise which` no longer resolve a bin name to a directory in another tool's bin path. Previously, `hunk`'s `skills/` directory blocked `npm:skills` with "Permission denied". [#&#8203;14193](https://redirect.github.com/jdx/mise/pull/14193)
- **`mise oci build` and multiple versions.** A tool pinned to several versions is now written to the embedded `/etc/mise/config.toml` as a single array, in resolution order. Previously each version got its own duplicate key, which made the config unreadable inside the container. [#&#8203;14200](https://redirect.github.com/jdx/mise/pull/14200) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **Encrypted files a sync can't unlock.** `mise bootstrap --adopt`, `mise dot pull` and `mise dot sync` now list every encrypted file they can't unlock in one error, instead of stopping at the first one. Damaged files still stop the sync immediately. [#&#8203;14208](https://redirect.github.com/jdx/mise/pull/14208)
- **Rollback and undo messages.** `mise dot rollback <path>` now names the checkpoint that saved the restored version, which matches `mise dot history --path`. Rollback and undo also name both checkpoints they record: the result, and the one holding the state before the operation. Previously, rollback could name a checkpoint that `history --path` didn't list. What gets restored is unchanged. [#&#8203;14207](https://redirect.github.com/jdx/mise/pull/14207), [#&#8203;14211](https://redirect.github.com/jdx/mise/pull/14211)
- **`mise dot status` on adopted machines.** On a machine that adopted a shared setup, it no longer prints "nothing configured in \[dotfiles]" while history tracks entries. [#&#8203;14206](https://redirect.github.com/jdx/mise/pull/14206)

##### New Contributors

- [@&#8203;jovial-liu](https://redirect.github.com/jovial-liu) made their first contribution in [#&#8203;14184](https://redirect.github.com/jdx/mise/pull/14184)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.10.5...v2026.10.6>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.5`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.5): : Java defaults to Temurin, per-machine and local-only dotfile history, packslip workflow pinning

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.4...vfox-v2026.10.5)

Java versions without a vendor prefix now install Eclipse Temurin builds. Dotfiles gain per-machine and local-only history, a `merge = "missing"` mode, and a secret check before `mise dot sync` publishes anything. Packslip tools can be pinned to the workflow that signs their releases. Several features documented as experimental now actually require `experimental = true`. The rest of the release is fixes across tasks, shims, config loading, bootstrap and backends.

##### Breaking Changes

- **`java@21`, `java@lts` and `java@latest` now install Temurin.** The default of `java.shorthand_vendor` changes from `openjdk` to `temurin`. The jdk.java.net OpenJDK builds stop at the next feature release, so `java@21` was stuck on 21.0.2 from January 2024. Vendor-prefixed requests (`openjdk-21`, `corretto-21`, ...) are not affected. Installed OpenJDK versions keep working, and `mise upgrade` offers the Temurin build. Shorthand versions now include Temurin's build suffix (for example `21.0.12+101.0.LTS`). Temurin has no builds of Java 9, 10 or 12–15, so use `openjdk-12` and similar for those. [#&#8203;14146](https://redirect.github.com/jdx/mise/pull/14146)
  - **Lockfiles:** existing shorthand lock entries record `shorthand_vendor = "openjdk"`. Without that setting, `mise install --locked` fails with `java@21 is not in the lockfile`. Either keep OpenJDK:
    ```toml
    [settings]
    java.shorthand_vendor = "openjdk"
    # or per project: java = "openjdk-21"
    ```
    or switch to Temurin with `mise lock --bump java` and commit the result.
- **Some experimental features now require `experimental = true`** (or `MISE_EXPERIMENTAL=1`) when you use them. Config that only mentions them still loads. [#&#8203;14114](https://redirect.github.com/jdx/mise/pull/14114)
  - Running a task with a `git::` remote `file`, including through `mise run --dry-run`, `mise watch`, or as a dependency. Commands that only inspect tasks (`tasks ls`, `tasks info`, `tasks deps`, `generate task-docs`) show the task from its TOML and warn once that the file wasn't fetched. These were documented as experimental but were missing the check in the codebase.
  - `git::` and `oci::` entries in `task_config.includes`. These are skipped with a warning.
  - OpenTelemetry export of `mise run` when an OTLP endpoint is set.
  - Installing `spinel:` tools.
- **`mise.local.toml` now overrides `mise.<env>.toml` in the same directory**, as the docs already said. Before, the committed environment file won in project directories, and also in `~/.config/mise` when the walk up from the cwd reached it. Within each directory the order is now, highest first: `mise.<env>.local.toml`, `mise.local.toml`, `mise.<env>.toml`, `mise.toml`. If you relied on the environment file winning, move those keys into `mise.<env>.local.toml`. [#&#8203;14148](https://redirect.github.com/jdx/mise/pull/14148)
- **MCP:** in the `mise://tasks` resource, `env` is now an array of env directive strings, the same format as `mise tasks ls --json`. It was always an empty object before. [#&#8203;14111](https://redirect.github.com/jdx/mise/pull/14111)
- **`mise settings set` and `add` refuse writes that would have no effect.** This covers early-init settings (`env`, `ceiling_paths`, `env_conf_d`, ...) written to a config file, and global-only settings (`yes`, `paranoid`, `trusted_config_paths`, ...) written with `--local`. The error says where the setting has to go: `miserc.toml`, the global config, or the `MISE_*` variable. [#&#8203;14126](https://redirect.github.com/jdx/mise/pull/14126)

##### Added

##### Dotfiles

- **Per-machine tracked files.** Some files describe the machine, such as a monitor layout. Use `variants = [{ machine = true }]` or `mise dot track --machine` to keep a separate history for each machine. Sync pushes every machine's version to the origin but never applies one machine's version on another. Each machine gets a generated name, which you can set with `[history] machine = "desk"`. A machine variant must be the entry's only variant and can't be combined with `encrypt`. Upgrade every machine that shares a setup before using it. [#&#8203;14062](https://redirect.github.com/jdx/mise/pull/14062)
  ```toml
  [dotfiles]
  "~/.config/hypr/monitors.lua" = { mode = "track", variants = [{ machine = true }] }
  ```
- **`mode = "track-local"`** (or `mise dot track --local`) keeps a file's history in a separate store on this machine, with no origin. The file never reaches the shared manifest, a commit or a push. Commands that name a path use the history that holds it. Use `mise dot --local history` or `mise dot --local undo` to work with the local history directly. `save`, `capture` and `watch` cover both histories. [#&#8203;14082](https://redirect.github.com/jdx/mise/pull/14082)
- **`merge = "missing"`** sets only the keys the target file doesn't have yet. Values an app writes itself, such as the model picked with `/model` in Codex or Claude Code, are left alone. Works with TOML, YAML and JSON. [#&#8203;14081](https://redirect.github.com/jdx/mise/pull/14081)
  ```toml
  [dotfiles]
  "~/.codex/config.toml/shared" = { merge = true }
  "~/.codex/config.toml/defaults" = { source = "codex/defaults.toml", merge = "missing" }
  ```
- Merge entries can leave out `source`. mise then reads the target's path under `dotfiles.root`. Switching an entry from `symlink` to `merge` now replaces a link that points at the merge source with a writable copy, so the app's own keys survive. [#&#8203;14076](https://redirect.github.com/jdx/mise/pull/14076)
- **`mise dot sync` refuses to publish saved versions that look like secrets.** It checks for provider tokens, private key blocks, and `*_KEY`/`*_TOKEN`/`*_SECRET`/`*_PASSWORD` assignments. Only versions the origin doesn't have yet are checked. The error names the file, line and version, never the value. `--allow-plaintext-history` skips the check. [#&#8203;14171](https://redirect.github.com/jdx/mise/pull/14171)
- `mise bootstrap --adopt <url> --take-remote-all` takes the repository's version of every file that differs, in one step. Combined with `--replace-history`, it also adopts the repository on a machine that already has history of its own. [#&#8203;14065](https://redirect.github.com/jdx/mise/pull/14065)
- `mise doctor` shows the dotfiles history repo path and the connected origin (URL, branch, sync mode), in both text and `--json` output. [#&#8203;14173](https://redirect.github.com/jdx/mise/pull/14173)

##### Bootstrap

- `[bootstrap.files]` and `[bootstrap.directories]` accept the same `os` selector as packages. Entries that don't match the host are skipped completely. [#&#8203;14058](https://redirect.github.com/jdx/mise/pull/14058)
  ```toml
  [bootstrap.files."/etc/docker/daemon.json"]
  os = "linux"
  source = "./files/docker-daemon.json"
  ```
- `fish = "auto"` in `[bootstrap.mise_shell_activate]` writes a block that runs `mise activate fish` in interactive shells and `--shims` everywhere else. [#&#8203;14172](https://redirect.github.com/jdx/mise/pull/14172)
- `[bootstrap]` is now allowed in config includes. It merges below the including file, so a shared baseline can declare packages, hooks and services, and the project still wins on any key it sets itself. [#&#8203;14176](https://redirect.github.com/jdx/mise/pull/14176)
- Systemd units accept `state = "absent"`, which stops, disables and deletes the matching mise-managed service and timer. `mise bootstrap unapply` now also removes the units an environment added. [#&#8203;14139](https://redirect.github.com/jdx/mise/pull/14139)

##### Tools and backends

- **Packslip `workflow` option.** It accepts only releases signed by the named GitHub Actions workflow on tags. It takes one workflow or a list, and an entry can name a ref (`release.yml@refs/heads/main`). It can't be combined with `pubkey`, `identity`, `identity_prefix` or `issuer`. [#&#8203;14075](https://redirect.github.com/jdx/mise/pull/14075), [#&#8203;14093](https://redirect.github.com/jdx/mise/pull/14093)
  ```toml
  [tools]
  "packslip:github.com/aubepkg/aube" = { version = "latest", workflow = ["release-plz.yml", "release.yml"] }
  ```
  - Registry entries for dagu, timoni, helmfile, fnox, hk, usage, communique, mr-boxington, pitchfork, aube and worktrunk are now pinned to the workflow that signs their releases. A `workflow` or `pubkey` you set yourself replaces the registry pin. [#&#8203;14092](https://redirect.github.com/jdx/mise/pull/14092), [#&#8203;14091](https://redirect.github.com/jdx/mise/pull/14091), [#&#8203;14073](https://redirect.github.com/jdx/mise/pull/14073) ([@&#8203;max-sixty](https://redirect.github.com/max-sixty))
- **MCP `install_tool` is implemented.** It installs the requested version, or the configured version, or latest, and returns the resolved version and install path. `install_tool` and `run_task` now refuse to act on untrusted config instead of trusting it on the client's behalf. [#&#8203;14111](https://redirect.github.com/jdx/mise/pull/14111)
- `mise installs migrate` no longer fails on installs it can't reinstall, such as withdrawn releases, signer changes or no network. It moves them into the identity layout as they are and leaves a link at the old path. It also handles lockfile-suffixed `~aube~`/`~uv~` directories correctly. [#&#8203;14079](https://redirect.github.com/jdx/mise/pull/14079)
- Registry: `mprocs` is renamed to `dekit` to match upstream, and `mprocs` stays as an alias. From v0.10.0 the binary is `dekit`. [#&#8203;14169](https://redirect.github.com/jdx/mise/pull/14169)

##### Changed

- Dotfile operations you can undo no longer ask for confirmation after showing their plan. This covers `dot pull`, `dot track`, `undo`, `rollback`, `connect` and `bootstrap --adopt`. Destructive ones still ask, such as `--replace-history`, `dot recover --keep-current` and `implode`. `--yes` and `MISE_YES` are still accepted. [#&#8203;14077](https://redirect.github.com/jdx/mise/pull/14077), [#&#8203;14071](https://redirect.github.com/jdx/mise/pull/14071), [#&#8203;14067](https://redirect.github.com/jdx/mise/pull/14067)
- Safe mode (`MISE_SAFE=1`) now ignores a project's `[bootstrap]`, `[dotfiles]` and `[dotfile_groups]`. Before, an untrusted repo could link files into `$HOME` or clone repositories. Global and system config still apply. [#&#8203;14110](https://redirect.github.com/jdx/mise/pull/14110)
- Node downloads are checked against the GPG signature for every version that publishes one. Before, only versions starting with `2` were checked. Set `node.gpg_verify = false` for mirrors without signatures. [#&#8203;14132](https://redirect.github.com/jdx/mise/pull/14132)
- `mise generate github-action` now uses `actions/checkout@v7` and `jdx/mise-action@v5` and no longer sets `MISE_EXPERIMENTAL`. `mise generate devcontainer` now adds `"postCreateCommand": "mise install"`. [#&#8203;14145](https://redirect.github.com/jdx/mise/pull/14145)

##### Deprecated

- `task.cache.stats_report`, `sops.age_recipients` and `plugin_autoupdate_last_check_duration` never had any effect. Setting one now prints a warning, and they will be removed in 2027.10.5. [#&#8203;14112](https://redirect.github.com/jdx/mise/pull/14112)
- `mise bootstrap launchd|systemd|macos-defaults`, the old `--only`/`--skip` part names (`launchd`, `systemd`, `defaults`, `shell`) and `mise direnv` now print a warning that names the replacement. They will be removed in 2027.10.4. [#&#8203;14113](https://redirect.github.com/jdx/mise/pull/14113)

##### Fixed

##### Tasks

- A raw or interactive task with `cache` enabled is now skipped when its sources are fresh and its cache key hasn't changed. Before, it ran every time. [#&#8203;14121](https://redirect.github.com/jdx/mise/pull/14121)
- A typo in `sources` that matches no files now prints a warning. `mise tasks deps <task>` now shows `depends_post` tasks. Scripts in `$MISE_CONFIG_DIR/tasks` load even without a global config file. [#&#8203;14122](https://redirect.github.com/jdx/mise/pull/14122)
- `mise run ./script` resolves relative to the current directory. [#&#8203;14120](https://redirect.github.com/jdx/mise/pull/14120)
- Script extensions are stripped only from file task names. [#&#8203;14159](https://redirect.github.com/jdx/mise/pull/14159)
- Requests to the remote task cache include the mbx-cache headers. [#&#8203;14147](https://redirect.github.com/jdx/mise/pull/14147)
- `mise generate task-stubs` skips hidden and global tasks. [#&#8203;14127](https://redirect.github.com/jdx/mise/pull/14127)

##### Shims and exec

- Shims, `mise x` and `mise env` no longer look up remote versions for lazy tools that aren't installed. With 20 lazy tools and no network, running an installed tool went from about 42s to 0.09s. [#&#8203;14063](https://redirect.github.com/jdx/mise/pull/14063)
- On Unix, a system wrapper that execs the same command name can no longer send an unconfigured shim into an endless loop. [#&#8203;14088](https://redirect.github.com/jdx/mise/pull/14088)
- Windows lazy shim names now match regardless of letter case. [#&#8203;14090](https://redirect.github.com/jdx/mise/pull/14090)
- `mise x [email protected]` installs the exact version when the version list times out, instead of failing with "couldn't exec process". [#&#8203;14164](https://redirect.github.com/jdx/mise/pull/14164)
- `mise doctor` recognizes a `mise` entry in the dedicated shims dir. [#&#8203;14069](https://redirect.github.com/jdx/mise/pull/14069)

##### Config, settings and CLI

- With an environment active and no `config.toml` yet, `mise use -g` creates `config.toml` instead of writing into `config.<env>.toml`. [#&#8203;14179](https://redirect.github.com/jdx/mise/pull/14179)
- Booleans set through `mise config set --type bool` and boolean settings accept `yes`/`no`/`1`/`0`. For `env.*` and other keys that aren't settings, only `true` and `false` become booleans, and anything else is stored as written. `mise latest tool@prefix:X` works, and `--log-level warn` is accepted. `mise doctor` prints the full chain when config fails to load. [#&#8203;14125](https://redirect.github.com/jdx/mise/pull/14125)
- Help output uses the command name you typed. [#&#8203;14123](https://redirect.github.com/jdx/mise/pull/14123)
- `mise edit` no longer overwrites an existing config when there is no terminal. [#&#8203;14124](https://redirect.github.com/jdx/mise/pull/14124)
- `.monorepo` markers are verified in paranoid mode. [#&#8203;14105](https://redirect.github.com/jdx/mise/pull/14105)
- Bad input now gives an error instead of a panic, in places including `mise config ls` and `mise prune` with an invalid tool version, the `--monorepo` flags, and `mise plugins uninstall --purge`. [#&#8203;14118](https://redirect.github.com/jdx/mise/pull/14118), [#&#8203;14094](https://redirect.github.com/jdx/mise/pull/14094) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562)), [#&#8203;14103](https://redirect.github.com/jdx/mise/pull/14103) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562)), [#&#8203;14116](https://redirect.github.com/jdx/mise/pull/14116), [#&#8203;14117](https://redirect.github.com/jdx/mise/pull/14117)
- `mise env --redacted` honors `redact = false` exclusions. `watch_files` hooks don't run in safe mode or with `--no-hooks`. [#&#8203;14109](https://redirect.github.com/jdx/mise/pull/14109), [#&#8203;14108](https://redirect.github.com/jdx/mise/pull/14108)
- `mise watch` passes watchexec flags through to watchexec. [#&#8203;14115](https://redirect.github.com/jdx/mise/pull/14115)
- Error and warning hints point at current commands, for example the PATH advice from `bootstrap remote` and the watchexec install hint. [#&#8203;14142](https://redirect.github.com/jdx/mise/pull/14142)
- If stderr can't be written to during a migration, mise no longer aborts. [#&#8203;14158](https://redirect.github.com/jdx/mise/pull/14158) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- When the default cache and temp directories overlap (the Windows default), temp files now go to `<temp>/mise-tmp`, so `mise cache clear` no longer deletes files that are still in use. [#&#8203;14134](https://redirect.github.com/jdx/mise/pull/14134)

##### Bootstrap and dotfiles

- `mise bootstrap` keeps writing the shell activation block into a startup file that is only tracked. [#&#8203;14135](https://redirect.github.com/jdx/mise/pull/14135)
- If one package manager fails, the others still run. [#&#8203;14138](https://redirect.github.com/jdx/mise/pull/14138)
- The firewall section is skipped on systems that don't support it. [#&#8203;14137](https://redirect.github.com/jdx/mise/pull/14137)
- Dry runs show hooks that use `exec()`. [#&#8203;14136](https://redirect.github.com/jdx/mise/pull/14136)
- Pulling onto a machine with no history now saves the files it replaces first, so `mise dot undo` restores them. `--replace-history` no longer fails on its first attempt. [#&#8203;14065](https://redirect.github.com/jdx/mise/pull/14065)
- `mise dot pull` says so when incoming history changes no files on this machine. [#&#8203;14066](https://redirect.github.com/jdx/mise/pull/14066)

##### Backends and plugins

- `mise install cargo:...` no longer uses an inactive `cargo-binstall` shim. It falls back to native binstall or `cargo install`. [#&#8203;14070](https://redirect.github.com/jdx/mise/pull/14070)
- With `npm.shell_out`, a mise shim from another data dir can no longer be run as `npm` and fork until the machine runs out of memory. [#&#8203;14084](https://redirect.github.com/jdx/mise/pull/14084) (originally found and fixed by [@&#8203;tfournet](https://redirect.github.com/tfournet))
- Java: changing `java.shorthand_vendor` takes effect without waiting for the cache to expire. Inline `java[release_type=ea]` is respected when listing and resolving versions. [#&#8203;14131](https://redirect.github.com/jdx/mise/pull/14131)
- Ruby precompiled URLs expand `{os}` and `{arch}` correctly. [#&#8203;14133](https://redirect.github.com/jdx/mise/pull/14133)
- `ls-remote` respects `disable_backends`. [#&#8203;14130](https://redirect.github.com/jdx/mise/pull/14130)
- SPM uses a custom `api_url` for cloning and skips lockfile URLs. [#&#8203;14129](https://redirect.github.com/jdx/mise/pull/14129)
- GitHub assets are size-checked even when there is no checksum. GitLab release lookups percent-encode the tag. [#&#8203;14128](https://redirect.github.com/jdx/mise/pull/14128), [#&#8203;14102](https://redirect.github.com/jdx/mise/pull/14102) ([@&#8203;thespags](https://redirect.github.com/thespags))
- Brew infers the formula version from the GitHub release tag path, and detaches a DMG left attached by an interrupted install. [#&#8203;14165](https://redirect.github.com/jdx/mise/pull/14165) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562)), [#&#8203;14177](https://redirect.github.com/jdx/mise/pull/14177)
- `git::` plugins install from a subdirectory, and `[plugins]` entries compare those sources correctly. Packslip entries in `[plugins]` install as vfox plugins. [#&#8203;14140](https://redirect.github.com/jdx/mise/pull/14140), [#&#8203;14150](https://redirect.github.com/jdx/mise/pull/14150) ([@&#8203;onokonem](https://redirect.github.com/onokonem)), [#&#8203;14141](https://redirect.github.com/jdx/mise/pull/14141)
- vfox: an env plugin with no source no longer makes every command fail. Embedded plugin files are found relative to the manifest directory. [#&#8203;14119](https://redirect.github.com/jdx/mise/pull/14119), [#&#8203;14167](https://redirect.github.com/jdx/mise/pull/14167)

##### Daemons and sandbox

- On Windows, deferred daemon commands are quoted for cmd.exe. Argv and Windows readiness probes run in the tool environment. [#&#8203;14055](https://redirect.github.com/jdx/mise/pull/14055), [#&#8203;14080](https://redirect.github.com/jdx/mise/pull/14080) ([@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- `--deny-env` clears inherited variables on Windows. On macOS, `allow_net` is rejected instead of writing invalid sandbox rules. [#&#8203;14107](https://redirect.github.com/jdx/mise/pull/14107), [#&#8203;14106](https://redirect.github.com/jdx/mise/pull/14106)

##### Security

- Remote task cache keys store env, `[vars]` and task arg values as `blake3:` digests instead of plaintext, so secret values are no longer uploaded. Existing cache entries miss once after upgrading. [#&#8203;14104](https://redirect.github.com/jdx/mise/pull/14104)
- URL credentials are redacted from HTTP debug logs and from npm Git-source and SSH diagnostics. [#&#8203;14155](https://redirect.github.com/jdx/mise/pull/14155), [#&#8203;14057](https://redirect.github.com/jdx/mise/pull/14057), [#&#8203;14064](https://redirect.github.com/jdx/mise/pull/14064)

##### Documentation

- The documentation site, CLI help and settings descriptions have been restructured and rewritten, and moved pages still resolve. [#&#8203;14097](https://redirect.github.com/jdx/mise/pull/14097), [#&#8203;14098](https://redirect.github.com/jdx/mise/pull/14098), [#&#8203;14096](https://redirect.github.com/jdx/mise/pull/14096), [#&#8203;14163](https://redirect.github.com/jdx/mise/pull/14163)

##### New Contributors

- [@&#8203;onokonem](https://redirect.github.com/onokonem) made their first contribution in [#&#8203;14150](https://redirect.github.com/jdx/mise/pull/14150)
- [@&#8203;max-sixty](https://redirect.github.com/max-sixty) made their first contribution in [#&#8203;14073](https://redirect.github.com/jdx/mise/pull/14073)

**Full Changelog**: <https://github.com/jdx/mise/compare/vfox-v2026.10.4...v2026.10.5>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.10.4`](https://redirect.github.com/jdx/mise/releases/tag/v2026.10.4): : Task-scoped fnox secrets, automatic global tool updates, and an opt-in identity install layout

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.10.3...vfox-v2026.10.4)

This release adds experimental task-scoped secrets backed by fnox, automatic updates for global tools, and an experimental opt-in install layout that names installations by what they contain. It also adds dotfile `merge` entries, `headers` auth for the `http:` backend, npm installs from git, and a set of prune, install and lockfile fixes.

##### Added

##### Secrets (experimental)

These need `mise settings experimental=true` and fnox 1.39.0 or newer. They are refused in safe mode.

- **`[secrets.fnox]` and `mise secrets ls`.** A project can name fnox as its secrets source in its own `mise.toml`. `mise secrets ls` lists key names and metadata but never values. `-J` prints JSON. Global, system and home-level `[secrets]` config is ignored, and `mise doctor` reports it. [#&#8203;13967](https://redirect.github.com/jdx/mise/pull/13967)

- **Tasks get only the secrets they list.** Add `secrets = [...]` to a task, or `#MISE secrets=[...]` in a file task header. Only that task gets the values, only while it runs, and they are redacted from its output. Dependencies, hooks, `mise env`, hook-env and shims get nothing. A task without grants never calls fnox. Tasks that list secrets need a trusted config, aren't artifact-cached, and ignore `--raw` unless the task sets `raw` or `interactive`. Hooks, `watch_files`, daemons and `mise bootstrap` can't run them, and remote or global-config tasks can't list secrets. [#&#8203;13974](https://redirect.github.com/jdx/mise/pull/13974)

  ```toml
  min_version = "2026.10.4"   # older mise rejects `secrets` on a task

  [secrets.fnox]
  profile = "prod"

  [tasks.deploy]
  depends = ["build"]                       # build receives nothing
  secrets = ["DEPLOY_KEY", "DATABASE_URL"]
  run = "./deploy.sh"
  ```

- **One-off grants from the command line.** `--secrets KEY[,KEY]` and `--secrets-all` work on `mise run`, `mise tasks run` and `mise x`. For `mise run`, only the tasks named on the command line get them, not their dependencies. `mise x` gets nothing unless you pass a flag, and it can't receive file secrets. Flags placed after the task name go to the task, and mise warns when that happens. [#&#8203;13975](https://redirect.github.com/jdx/mise/pull/13975)

  ```sh
  mise run --secrets STRIPE_KEY deploy
  mise x --secrets GH_TOKEN -- gh release list
  ```

- **`{{ secrets.X }}` in task env values.** A task can build an env var from a secret, for example `env.PGURL = "postgres://app:{{ secrets.DB_PASSWORD }}@db/app"`. The reference counts as the grant. References are rejected in `run`, `[env]`, `[vars]` and other fields. [#&#8203;13978](https://redirect.github.com/jdx/mise/pull/13978)

- **fnox daemon cache.** If the project's fnox config enables the daemon and it's running, an interactive run reads cached values over its socket without starting an fnox process. mise never starts the daemon itself. CI, non-TTY runs and Windows always use the fnox CLI. [#&#8203;13979](https://redirect.github.com/jdx/mise/pull/13979)

##### Automatic global tool updates

- **`auto_update` for global tools.** Set `auto_update` on a tool in your global config. When a shim or `mise x` is about to run that tool and its check interval is due, mise upgrades it within the configured range and then runs the new version. `true` uses the new `tool_update.check_duration` setting (default 24h), and a duration string sets that tool's own interval (minimum 1h). [#&#8203;14026](https://redirect.github.com/jdx/mise/pull/14026)

  - Exact pins never update.
  - It never runs offline, in CI, with `locked = true`, from tasks or hook-env, or from project configs.
  - If an update fails, mise warns and runs the installed version. The failure shows in `mise doctor`.

  ```toml
  # ~/.config/mise/config.toml
  [tools]
  claude = { version = "latest", auto_update = true }
  node = { version = "22", auto_update = "6h" }   # newest 22.x, never 23
  ```

- **Background `tool-update` service.** A built-in bootstrap service checks `auto_update` tools hourly, so launches don't wait for downloads and tools run directly from PATH under activation also stay current. It installs a systemd user unit, LaunchAgent or scheduled task. [#&#8203;14040](https://redirect.github.com/jdx/mise/pull/14040)

  ```toml
  [bootstrap.services.mise-tool-update]
  builtin = "tool-update"
  ```

##### Identity install layout (experimental, opt-in)

- **`install_layout = "identity"`.** With `experimental = true`, each installation lives in `installs/<label>-<hash>/`. The hash covers the canonical backend, version, platform and install-affecting options. `installs/<tool>/<version>` becomes a link to that directory, a real junction on Windows. As a result: [#&#8203;13951](https://redirect.github.com/jdx/mise/pull/13951)

  - `age` and `aqua:FiloSottile/age` share one installation.
  - Variants of one version with different options can exist side by side.
  - Windows IDEs can follow the version links.

  Existing installs keep working and aren't moved. Nothing changes unless you set this. `MISE_INSTALL_LAYOUT=identity` also turns it on. See the new install layout docs.
- On Windows, identity-layout installations go into the shorter `%LOCALAPPDATA%\mise\i\`, while version links stay in `installs\`. `MISE_INSTALL_STORE_DIR` sets the location on any platform. [#&#8203;13952](https://redirect.github.com/jdx/mise/pull/13952)
- `mise installs ls` lists installations as `selected`, `pinned` or `shared`. `mise installs select <dir>` chooses which installation requests without a lockfile use. When several installations match and none is selected, mise lists them instead of guessing. [#&#8203;13953](https://redirect.github.com/jdx/mise/pull/13953)
- `mise installs migrate [--dry-run] [TOOL[@VERSION]]` reinstalls legacy installations into the new layout and leaves a link at the old path. If a run is interrupted, the next run recovers it. [#&#8203;13955](https://redirect.github.com/jdx/mise/pull/13955)
- In the layout, `mise backends switch` installs the new backend's version and points the link at it. `mise where` and the other commands now resolve versions named on the command line the same way, and `mise where` lists variants instead of picking one. [#&#8203;13957](https://redirect.github.com/jdx/mise/pull/13957)
- `mise prune` handles templated tool versions such as `node = "{{ vars.node }}"`. It uses snapshots of what each project last rendered and keeps installs when it isn't sure they are unused. [#&#8203;14025](https://redirect.github.com/jdx/mise/pull/14025)

##### Other additions

- **Dotfile `merge` entries.** A `merge = true` entry sets only the keys from its source in a JSON, TOML or YAML file, and leaves keys added by the application (Codex, Claude Code, etc.) alone. TOML and YAML keep comments and formatting. `mise dot status` and `mise dot diff` only report drift in those keys. A target that doesn't parse is never overwritten. [#&#8203;14012](https://redirect.github.com/jdx/mise/pull/14012)

  ```toml
  [dotfiles]
  "~/.codex/config.toml/shared" = { source = "codex/shared.toml", merge = true }
  ```

- **`headers` for the `http:` backend.** You can now authenticate with bearer tokens or API keys, for example to download OCI blobs from `ghcr.io`. Values are templates. Headers are sent with downloads, `version_list_url` and `checksum_url` requests, and dropped on cross-host redirects unless the host is listed in `headers_forward`. Changing a token doesn't trigger a reinstall. [#&#8203;14022](https://redirect.github.com/jdx/mise/pull/14022)

  ```toml
  [tools."http:polaris"]
  version = "0.9.2"
  headers = { Authorization = "Bearer {{ env.GITHUB_TOKEN | b64_encode }}" }
  ```

- **npm packages from git.** `git+` URLs and `github:`, `gitlab:` and `bitbucket:` specs now install. The version is a git ref, and `latest` is the default branch. [#&#8203;14044](https://redirect.github.com/jdx/mise/pull/14044)

  ```sh
  mise use 'npm:github:owner/[email protected]'
  ```

- **`settings.write_targets`.** Sends new global `[tools]`, `[bootstrap.packages]` and `[dotfiles]` entries to separate `conf.d` files. Existing entries are updated where they're already declared. [#&#8203;14018](https://redirect.github.com/jdx/mise/pull/14018)

  ```toml
  [settings.write_targets]
  tools = "~/.config/mise/conf.d/10-tools.toml"
  dotfiles = "~/.config/mise/conf.d/30-dotfiles.toml"
  ```

- **`prune.exclude`** (or `MISE_PRUNE_EXCLUDE`) lists tools that `mise prune`, `mise ls --prunable` and upgrade pruning never remove. Short and full names both work, e.g. `node` or `aqua:BurntSushi/ripgrep`. [#&#8203;14030](https://redirect.github.com/jdx/mise/pull/14030)

- **`lockfile_auto_prune = false`** keeps lockfile entries for tools missing from the active config. This is useful when profiles share one lockfile. The default (`tr

> [!IMPORTANT]
> ✂ PR body was truncated to here.


</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjE1NS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-13 09:10:53 -07:00
renovate[bot] 399f493808 chore(deps): update dependency supertest to v7.3.1 (#721)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [supertest](https://redirect.github.com/ladjs/supertest) | [`7.2.2` → `7.3.1`](https://renovatebot.com/diffs/npm/supertest/7.2.2/7.3.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/supertest/7.3.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/supertest/7.2.2/7.3.1?slim=true) |

---

### Release Notes

<details>
<summary>ladjs/supertest (supertest)</summary>

### [`v7.3.1`](https://redirect.github.com/forwardemail/supertest/releases/tag/v7.3.1)

[Compare Source](https://redirect.github.com/ladjs/supertest/compare/v7.3.0...v7.3.1)

- Merge pull request [#&#8203;907](https://redirect.github.com/ladjs/supertest/issues/907) from JH8459/fix/ephemeral-loopback-bind  [`884cd26`](https://redirect.github.com/ladjs/supertest/commit/884cd26)
- fix: bind ephemeral server to the loopback address it connects to  [`e74a1e0`](https://redirect.github.com/ladjs/supertest/commit/e74a1e0)

***

### [`v7.3.0`](https://redirect.github.com/forwardemail/supertest/releases/tag/v7.3.0)

[Compare Source](https://redirect.github.com/ladjs/supertest/compare/v7.2.2...v7.3.0)

- fix: stabilize ephemeral server requests and assertions  [`71dc5fb`](https://redirect.github.com/ladjs/supertest/commit/71dc5fb)
- Merge pull request [#&#8203;883](https://redirect.github.com/ladjs/supertest/issues/883) from forwardemail/dependabot/npm_and_yarn/multi-acd8535d99  [`3b5ba5c`](https://redirect.github.com/ladjs/supertest/commit/3b5ba5c)
- Merge pull request [#&#8203;886](https://redirect.github.com/ladjs/supertest/issues/886) from forwardemail/dependabot/npm_and_yarn/picomatch-2.3.2  [`c3419b2`](https://redirect.github.com/ladjs/supertest/commit/c3419b2)
- Merge pull request [#&#8203;887](https://redirect.github.com/ladjs/supertest/issues/887) from forwardemail/dependabot/npm_and_yarn/lodash-4.18.1  [`7e409db`](https://redirect.github.com/ladjs/supertest/commit/7e409db)
- Merge pull request [#&#8203;898](https://redirect.github.com/ladjs/supertest/issues/898) from forwardemail/dependabot/npm_and_yarn/brace-expansion-1.1.21  [`b55a7f9`](https://redirect.github.com/ladjs/supertest/commit/b55a7f9)
- Merge pull request [#&#8203;899](https://redirect.github.com/ladjs/supertest/issues/899) from forwardemail/dependabot/npm_and_yarn/browserslist-4.29.0  [`7a5deaa`](https://redirect.github.com/ladjs/supertest/commit/7a5deaa)
- Merge pull request [#&#8203;900](https://redirect.github.com/ladjs/supertest/issues/900) from forwardemail/dependabot/npm_and_yarn/fast-uri-3.1.8  [`a75f6ee`](https://redirect.github.com/ladjs/supertest/commit/a75f6ee)
- Merge pull request [#&#8203;901](https://redirect.github.com/ladjs/supertest/issues/901) from forwardemail/dependabot/npm_and_yarn/js-yaml-3.15.2  [`c2cb33e`](https://redirect.github.com/ladjs/supertest/commit/c2cb33e)
- Merge pull request [#&#8203;896](https://redirect.github.com/ladjs/supertest/issues/896) from pnookala-godaddy/codex/ephemeral-loopback-bind  [`7fb34e7`](https://redirect.github.com/ladjs/supertest/commit/7fb34e7)
- chore(deps-dev): bump js-yaml from 3.14.2 to 3.15.2  [`c357584`](https://redirect.github.com/ladjs/supertest/commit/c357584)
- chore(deps-dev): bump browserslist from 4.25.1 to 4.29.0  [`2a01b57`](https://redirect.github.com/ladjs/supertest/commit/2a01b57)
- chore(deps-dev): bump fast-uri from 3.0.6 to 3.1.8  [`343394d`](https://redirect.github.com/ladjs/supertest/commit/343394d)
- chore(deps-dev): bump brace-expansion from 1.1.12 to 1.1.21  [`6b203e9`](https://redirect.github.com/ladjs/supertest/commit/6b203e9)
- Merge pull request [#&#8203;881](https://redirect.github.com/ladjs/supertest/issues/881) from forwardemail/dependabot/npm_and_yarn/qs-6.14.2  [`d40ca7e`](https://redirect.github.com/ladjs/supertest/commit/d40ca7e)
- fix: match ephemeral server address family  [`b6f5995`](https://redirect.github.com/ladjs/supertest/commit/b6f5995)
- chore(deps-dev): bump lodash from 4.17.21 to 4.18.1  [`81766ca`](https://redirect.github.com/ladjs/supertest/commit/81766ca)
- chore(deps-dev): bump picomatch from 2.3.1 to 2.3.2  [`985ac7c`](https://redirect.github.com/ladjs/supertest/commit/985ac7c)
- chore(deps): bump minimatch  [`58baa5f`](https://redirect.github.com/ladjs/supertest/commit/58baa5f)
- chore(deps): bump qs from 6.14.1 to 6.14.2  [`c406e82`](https://redirect.github.com/ladjs/supertest/commit/c406e82)

***

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-13 09:10:45 -07:00
renovate[bot] 20f2714edd chore(deps): update redis docker digest to 2c2dff7 (#719)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `6f81e89` → `2c2dff7` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-13 09:10:37 -07:00
renovate[bot] bdcef9fd37 chore(deps): update postgres:18 docker digest to 74935e7 (#718)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [postgres](https://hub.docker.com/_/postgres) ([source](https://redirect.github.com/docker-library/postgres)) | digest | `5a5a84b` → `74935e7` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzQuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-13 09:10:25 -07:00
Will Schurman 593fb10c99 v0.71.0 2026-10-09 11:09:24 -07:00
Vatsal Patel 3c96f3b9ea fix: declare runtime dependencies imported by published packages (#717) 2026-10-09 09:14:43 -07:00
Will Schurman 84f83071f3 v0.70.0 2026-10-05 10:24:41 -07:00
Will Schurman 🦦 13ce74e93a feat: add transient error for PostgreSQL lock timeouts (#716)
# Why

Re-implementation of #714 on top of #715.

# How

Prompt:

> add new error for pg code 55P03, use PR 714 as reference but a new error class

# Test Plan

CI
2026-10-05 10:08:28 -07:00
Will Schurman 🦦 6de04055e7 feat!: move knex-specific database adapter errors into entity-database-adapter-knex (#715)
# Why

While reviewing #714, noticed that it was probably better if these knex/postgres-specific errors live in their package rather than the root package.

This is a breaking change but one that is detectable via tsc so within the scope of what's allowed via minor version bump.

# How

Have claude move them. Prompt: 

> move knex-specific entity errors into the entity-database-adapter-knex package. no need for backwards
  compatibility

# Test Plan

CI
2026-10-05 10:05:23 -07:00
renovate[bot] ba74662842 chore(deps): update jest to v30.5.2 (#712)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@jest/globals](https://redirect.github.com/jestjs/jest) ([source](https://redirect.github.com/jestjs/jest/tree/HEAD/packages/jest-globals)) | [`30.5.1` → `30.5.2`](https://renovatebot.com/diffs/npm/@jest%2fglobals/30.5.1/30.5.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@jest%2fglobals/30.5.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@jest%2fglobals/30.5.1/30.5.2?slim=true) |
| [jest](https://jestjs.io/) ([source](https://redirect.github.com/jestjs/jest/tree/HEAD/packages/jest)) | [`30.5.1` → `30.5.2`](https://renovatebot.com/diffs/npm/jest/30.5.1/30.5.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/jest/30.5.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jest/30.5.1/30.5.2?slim=true) |

---

### Release Notes

<details>
<summary>jestjs/jest (@&#8203;jest/globals)</summary>

### [`v30.5.2`](https://redirect.github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3052)

[Compare Source](https://redirect.github.com/jestjs/jest/compare/v30.5.1...v30.5.2)

##### Features

- `[@jest/transform]` Strip TypeScript types with Node when no transformer claims a `.ts`, `.mts` or `.cts` file ([#&#8203;16421](https://redirect.github.com/jestjs/jest/pull/16421))

##### Fixes

- `[jest-core, jest-haste-map, jest-transform]` Keep `require('../package.json')` external when bundling, so `jest --version` and the transform and haste-map cache keys report the released version instead of the previous one ([#&#8203;16422](https://redirect.github.com/jestjs/jest/pull/16422))
- `[jest-each]` Escape a table row's keys before building the `$variable` interpolation `RegExp`, so a column name such as `count(*)` no longer fails the whole table with `Invalid regular expression`, and a `.` or `|` in a column name is matched literally ([#&#8203;16345](https://redirect.github.com/jestjs/jest/pull/16345))
- `[@jest/source-map]` Resolve absolute Windows paths in a source map's `sources` and `sourceRoot` again, instead of appending them to the transformed file's directory ([#&#8203;16439](https://redirect.github.com/jestjs/jest/pull/16439))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-05 08:44:51 -07:00
renovate[bot] 17bd06e67e chore(deps): update dependency @types/node to v24.19.1 (#713)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`24.13.5` → `24.19.1`](https://renovatebot.com/diffs/npm/@types%2fnode/24.13.5/24.19.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/24.19.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/24.13.5/24.19.1?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-05 08:41:40 -07:00
renovate[bot] 69ef7aa0c9 chore(deps): update dependency pg to v8.23.1 (#711)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pg](https://redirect.github.com/brianc/node-postgres) ([source](https://redirect.github.com/brianc/node-postgres/tree/HEAD/packages/pg)) | [`8.23.0` → `8.23.1`](https://renovatebot.com/diffs/npm/pg/8.23.0/8.23.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/pg/8.23.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pg/8.23.0/8.23.1?slim=true) |

---

### Release Notes

<details>
<summary>brianc/node-postgres (pg)</summary>

### [`v8.23.1`](https://redirect.github.com/brianc/node-postgres/compare/[email protected]@8.23.1)

[Compare Source](https://redirect.github.com/brianc/node-postgres/compare/[email protected]@8.23.1)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-05 08:41:31 -07:00
renovate[bot] 8db4fbbd8a chore(deps): update redis docker digest to 6f81e89 (#710)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `718f745` → `6f81e89` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-10-05 08:41:27 -07:00
Will Schurman c22ed50e80 v0.69.0 2026-09-29 08:34:30 -07:00
renovate[bot] 9dcbc3da23 chore(deps): update dependency ip-address to v10.5.1 [security] (#709)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [ip-address](https://redirect.github.com/beaugunderson/ip-address) | [`10.4.0` → `10.5.1`](https://renovatebot.com/diffs/npm/ip-address/10.4.0/10.5.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/ip-address/10.5.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ip-address/10.4.0/10.5.1?slim=true) |

---

### ip-address: no classifier recognizes the NAT64 local-use range 64:ff9b:1::/48, allowing SSRF and trust-boundary bypass
[CVE-2026-101910](https://nvd.nist.gov/vuln/detail/CVE-2026-101910) / [GHSA-2vr4-cq9g-pvrc](https://redirect.github.com/advisories/GHSA-2vr4-cq9g-pvrc)

<details>
<summary>More information</summary>

#### Details
##### Summary

No classifier on `Address6` recognizes the NAT64 local-use range `64:ff9b:1::/48` (RFC 8215). `isPrivate()`, `isLoopback()`, `isLinkLocal()` and their siblings all return `false` for every address in it, so an internal IPv4 destination written through a local-use NAT64 prefix (`64:ff9b:1:7f00:0:100::` for `127.0.0.1`, `64:ff9b:1:a9fe:a9:fe00::` for `169.254.169.254`) reads as an ordinary global address. `getType()` names the range `'NAT64 (local-use)'`, so the library knows what the address is and classifies it as nothing.

An application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) will classify an internal target as external and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach, such as a loopback service or a cloud metadata endpoint.

##### Details

The fix for GHSA-22jq-vg5j-6vgg (10.2.1) classifies IPv4-mapped (`::ffff:0:0/96`) and NAT64 well-known (`64:ff9b::/96`) addresses by their embedded IPv4 address: `embeddedIPv4()` in `src/ipv6.ts` decodes the trailing 32 bits and every special-use classifier delegates to the resulting `Address4`. Those are the only two ranges `embeddedIPv4()` handles.

The local-use range differs in kind from the well-known prefix, which is why extending the decoder does not fix it. RFC 8215 reserves `64:ff9b:1::/48` so an operator can carve their own NAT64 prefix out of it, of any length RFC 6052 allows (`/48`, `/56`, `/64`, or `/96`). Where the IPv4 address sits depends on that choice: `127.0.0.1` is `64:ff9b:1:7f00:0:100::` under a `/48` prefix and `64:ff9b:1::7f00:1` under a `/96` prefix, and each of those decodes to a different address under the other prefix length. There is no single embedded IPv4 address for the library to classify by.

What is well-defined is the range as a whole. The IANA IPv6 Special-Purpose Address Registry lists `64:ff9b:1::/48` as not globally reachable, and Python's `ipaddress` module reports `is_private` as `True` and `is_global` as `False` for every address in it. `isPrivate()` covered ULA (`fc00::/7`) plus the decoded mapped and well-known cases, and nothing in the local-use range.

##### Affected versions

`>= 10.2.0, <= 10.5.0`. The `is*` classification API was extended to `Address6` in 10.2.0; releases before it do not expose the method and are not affected through this vector.

##### Impact

Every address in `64:ff9b:1::/48` parses successfully, `isValid()` is `true`, and no classifier catches it. Which internal target is reached depends on the NAT64 prefix deployed on the server's network; the well-known prefix column is the patched control from GHSA-22jq-vg5j-6vgg.

| Internal target | Well-known form | Classified | Local-use form (`/48` prefix) | Classified |
|---|---|---|---|---|
| `127.0.0.1` | `64:ff9b::7f00:1` | loopback | `64:ff9b:1:7f00:0:100::` | nothing |
| `10.0.0.1` | `64:ff9b::a00:1` | private | `64:ff9b:1:a00:0:100::` | nothing |
| `169.254.169.254` | `64:ff9b::a9fe:a9fe` | link-local | `64:ff9b:1:a9fe:a9:fe00::` | nothing |
| `192.168.1.1` | `64:ff9b::c0a8:101` | private | `64:ff9b:1:c0a8:1:100::` | nothing |

##### Reachability

Reaching an internal host through one of these addresses requires that the server's network run a NAT64 translator on a prefix inside `64:ff9b:1::/48` and that the attacker guess or know the prefix length. That is the same precondition the well-known prefix carries (a translator on `64:ff9b::/96`), with the additional constraint that the prefix is operator-chosen rather than fixed. The severity reflects that precondition.

##### Proof of concept

`npm i [email protected]`, then:

```js
const { Address6 } = require('ip-address');

// A guard of the shape the library documents.
function isBlocked(host) {
  const a = new Address6(host);
  return a.isLoopback() || a.isLinkLocal() || a.isPrivate() || a.isMulticast() || a.isUnspecified();
}

for (const h of ['64:ff9b::7f00:1', '64:ff9b:1:7f00:0:100::', '64:ff9b:1::7f00:1']) {
  console.log(isBlocked(h) ? 'BLOCK' : 'ALLOW', h, '-> getType()', new Address6(h).getType());
}
```

On affected versions:

```
BLOCK 64:ff9b::7f00:1 -> getType() NAT64 (well-known)
ALLOW 64:ff9b:1:7f00:0:100:: -> getType() NAT64 (local-use)
ALLOW 64:ff9b:1::7f00:1 -> getType() NAT64 (local-use)
```

##### Remediation

Upgrade to the patched release. In the fix, `isPrivate()` returns `true` for every address in `64:ff9b:1::/48`, alongside ULA and the decoded mapped and well-known cases. The range is reported private as a whole rather than by a decoded IPv4 address, for the reason given above; `toAddress4Nat64(prefix)` remains the way to decode an address under a known deployment prefix. `isLoopback()` and `isLinkLocal()` are unchanged for this range, since without the prefix length the library cannot know which IPv4 address is embedded.

If you cannot upgrade immediately, test the range directly:

```js
const NAT64_LOCAL_USE = new Address6('64:ff9b:1::/48');
const localUse = new Address6(host).isHostInSubnet(NAT64_LOCAL_USE);
```

##### A note on SSRF defense

These methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the *resolved* IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.

#### Severity
- CVSS Score: 6.9 / 10 (Medium)
- Vector String: `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N`

#### References
- [https://github.com/beaugunderson/ip-address/security/advisories/GHSA-2vr4-cq9g-pvrc](https://redirect.github.com/beaugunderson/ip-address/security/advisories/GHSA-2vr4-cq9g-pvrc)
- [https://nvd.nist.gov/vuln/detail/CVE-2026-101910](https://nvd.nist.gov/vuln/detail/CVE-2026-101910)
- [https://github.com/beaugunderson/ip-address/commit/ab3dc88bcf5374344168a2ba075ca7ac4ff257f8](https://redirect.github.com/beaugunderson/ip-address/commit/ab3dc88bcf5374344168a2ba075ca7ac4ff257f8)
- [https://github.com/beaugunderson/ip-address/releases/tag/v10.5.1](https://redirect.github.com/beaugunderson/ip-address/releases/tag/v10.5.1)
- [https://github.com/advisories/GHSA-2vr4-cq9g-pvrc](https://redirect.github.com/advisories/GHSA-2vr4-cq9g-pvrc)

This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-2vr4-cq9g-pvrc) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>beaugunderson/ip-address (ip-address)</summary>

### [`v10.5.1`](https://redirect.github.com/beaugunderson/ip-address/compare/v10.5.0...38b02d73cfc0724f9ad61c65b6328e116eac03ec)

[Compare Source](https://redirect.github.com/beaugunderson/ip-address/compare/v10.5.0...v10.5.1)

### [`v10.5.0`](https://redirect.github.com/beaugunderson/ip-address/releases/tag/v10.5.0)

[Compare Source](https://redirect.github.com/beaugunderson/ip-address/compare/v10.4.0...v10.5.0)

##### What's Changed

- Honor the fromURL graceful-failure contract for non-IPv6 hosts by [@&#8203;beaugunderson](https://redirect.github.com/beaugunderson) in [#&#8203;218](https://redirect.github.com/beaugunderson/ip-address/pull/218)
- Correct the documentation where it disagreed with the library by [@&#8203;beaugunderson](https://redirect.github.com/beaugunderson) in [#&#8203;219](https://redirect.github.com/beaugunderson/ip-address/pull/219)

**Full Changelog**: <https://github.com/beaugunderson/ip-address/compare/v10.4.0...v10.5.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-29 08:23:06 -07:00
Will Schurman 🦦 e87e2a3a28 feat: add ID-based and field-based knex database loaders for use with locking modifiers (#706) 2026-09-28 16:29:15 -07:00
renovate[bot] 68e222f512 chore(deps): update postgres:18 docker digest to 5a5a84b (#707)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [postgres](https://hub.docker.com/_/postgres) ([source](https://redirect.github.com/docker-library/postgres)) | digest | `86c951e` → `5a5a84b` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-28 08:37:30 -07:00
renovate[bot] e404297b30 chore(deps): update redis docker digest to 718f745 (#708)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `8a1efc5` → `718f745` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-28 08:37:26 -07:00
Will Schurman cbf51c0488 v0.68.0 2026-09-25 08:45:56 -07:00
Will Schurman 🦦 243a0386fb feat: add forShare and skipLocked selection modifiers to knex loader (#705) 2026-09-24 15:53:40 -07:00
Will Schurman 🦦 727f16662d feat: add forUpdate to knex loader (#704) 2026-09-23 15:29:41 -07:00
renovate[bot] 0e1b9f9452 chore(deps): update dependency @types/node to v24.13.5 (#703)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`24.13.4` → `24.13.5`](https://renovatebot.com/diffs/npm/@types%2fnode/24.13.4/24.13.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/24.13.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/24.13.4/24.13.5?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-21 09:19:23 -07:00
renovate[bot] 52327bd56c chore(deps): update dependency @tsconfig/bases to v1.0.28 (#702)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@tsconfig/bases](https://redirect.github.com/tsconfig/bases) ([source](https://redirect.github.com/tsconfig/bases/tree/HEAD/bases)) | [`1.0.27` → `1.0.28`](https://renovatebot.com/diffs/npm/@tsconfig%2fbases/1.0.27/1.0.28) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@tsconfig%2fbases/1.0.28?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@tsconfig%2fbases/1.0.27/1.0.28?slim=true) |

---

### Release Notes

<details>
<summary>tsconfig/bases (@&#8203;tsconfig/bases)</summary>

### [`v1.0.28`](https://redirect.github.com/tsconfig/bases/compare/270039fbe015427858d03d84a8549d113aac0b76...270039fbe015427858d03d84a8549d113aac0b76)

[Compare Source](https://redirect.github.com/tsconfig/bases/compare/270039fbe015427858d03d84a8549d113aac0b76...270039fbe015427858d03d84a8549d113aac0b76)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-21 09:17:59 -07:00
renovate[bot] af121ed765 chore(deps): update redis docker digest to 8a1efc5 (#701)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `298e5b3` → `8a1efc5` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-21 09:17:53 -07:00
renovate[bot] ae9aab60bb chore(deps): update postgres:18 docker digest to 86c951e (#700)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [postgres](https://hub.docker.com/_/postgres) ([source](https://redirect.github.com/docker-library/postgres)) | digest | `4ef4dbc` → `86c951e` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-09-21 09:17:41 -07:00
Will Schurman 9629e36e0d v0.67.0 2026-09-18 12:23:16 -07:00
Will Schurman 🦦 4af5ede87d feat!: add user-facing denial reason to privacy policies (#699) 2026-09-18 10:30:50 -07:00
renovate[bot] fa5af4783f chore(deps): update node.js to v24.21.0 (#698)
> ℹ️ **Note**
> 
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [node](https://nodejs.org) ([source](https://redirect.github.com/nodejs/node)) | tools | minor | `24.18.1` → `24.21.0` |

---

### Release Notes

<details>
<summary>nodejs/node (node)</summary>

### [`v24.21.0`](https://redirect.github.com/nodejs/node/releases/tag/v24.21.0): 2026-09-08, Version 24.21.0 'Krypton' (LTS), @&#8203;aduh95

[Compare Source](https://redirect.github.com/nodejs/node/compare/v24.20.0...v24.21.0)

##### Notable Changes

- \[[`71106e1f17`](https://redirect.github.com/nodejs/node/commit/71106e1f17)] - **crypto**: update root certificates to NSS 3.126 (Node.js GitHub Bot) [#&#8203;65495](https://redirect.github.com/nodejs/node/pull/65495)
- \[[`afca0a912d`](https://redirect.github.com/nodejs/node/commit/afca0a912d)] - **(SEMVER-MINOR)** **crypto**: support loading private keys through STORE loaders (Filip Skokan) [#&#8203;63949](https://redirect.github.com/nodejs/node/pull/63949)
- \[[`6274fccbd9`](https://redirect.github.com/nodejs/node/commit/6274fccbd9)] - **deps**: update OpenSSL to 3.5.8 (Node.js GitHub Bot) [#&#8203;65542](https://redirect.github.com/nodejs/node/pull/65542)
- \[[`53cba013c7`](https://redirect.github.com/nodejs/node/commit/53cba013c7)] - **deps**: update Undici to 7.29.1 (Node.js GitHub Bot) [#&#8203;65789](https://redirect.github.com/nodejs/node/pull/65789)
- \[[`0529772798`](https://redirect.github.com/nodejs/node/commit/0529772798)] - **(SEMVER-MINOR)** **lib,src**: improve histogram implementation (James M Snell) [#&#8203;65024](https://redirect.github.com/nodejs/node/pull/65024)
- \[[`41c7062b81`](https://redirect.github.com/nodejs/node/commit/41c7062b81)] - **(SEMVER-MINOR)** **net**: improve performance of `net.BlockList` (James M Snell) [#&#8203;64974](https://redirect.github.com/nodejs/node/pull/64974)
- \[[`5197b5a3c5`](https://redirect.github.com/nodejs/node/commit/5197b5a3c5)] - **(SEMVER-MINOR)** **perf\_hooks**: add statistical hypothesis testing to histogram (James M Snell) [#&#8203;65416](https://redirect.github.com/nodejs/node/pull/65416)
- \[[`35c635b032`](https://redirect.github.com/nodejs/node/commit/35c635b032)] - **(SEMVER-MINOR)** **util**: add non-throwing `MIMEType.parse` (James M Snell) [#&#8203;64965](https://redirect.github.com/nodejs/node/pull/64965)

##### Commits

- \[[`84d706cb9b`](https://redirect.github.com/nodejs/node/commit/84d706cb9b)] - **assert**: improve documentation wording (Kamal Rawal) [#&#8203;64953](https://redirect.github.com/nodejs/node/pull/64953)
- \[[`90d127db33`](https://redirect.github.com/nodejs/node/commit/90d127db33)] - **(SEMVER-MINOR)** **benchmark**: add --analyze mode to compare.js (James M Snell) [#&#8203;65416](https://redirect.github.com/nodejs/node/pull/65416)
- \[[`ad1d7884c3`](https://redirect.github.com/nodejs/node/commit/ad1d7884c3)] - **benchmark**: add test-only and mock timers cases (Luan Muniz) [#&#8203;64097](https://redirect.github.com/nodejs/node/pull/64097)
- \[[`1d8f045914`](https://redirect.github.com/nodejs/node/commit/1d8f045914)] - **benchmark**: apply `highWaterMark` in webstreams `pipe-to` (Matteo Collina) [#&#8203;65138](https://redirect.github.com/nodejs/node/pull/65138)
- \[[`ed7ba3c993`](https://redirect.github.com/nodejs/node/commit/ed7ba3c993)] - **benchmark**: complete the sqlite is-transaction fix (Edy Silva) [#&#8203;65218](https://redirect.github.com/nodejs/node/pull/65218)
- \[[`c8837e1aa3`](https://redirect.github.com/nodejs/node/commit/c8837e1aa3)] - **benchmark**: add test runner hooks and options (Luan Muniz) [#&#8203;63754](https://redirect.github.com/nodejs/node/pull/63754)
- \[[`2ba8661e23`](https://redirect.github.com/nodejs/node/commit/2ba8661e23)] - **buffer**: prevent string write offset overflow (Matteo Collina) [#&#8203;65043](https://redirect.github.com/nodejs/node/pull/65043)
- \[[`cc9ee6c2ae`](https://redirect.github.com/nodejs/node/commit/cc9ee6c2ae)] - **buffer**: treat detached ArrayBuffers as empty (Archkon) [#&#8203;64504](https://redirect.github.com/nodejs/node/pull/64504)
- \[[`5a5d73e4c5`](https://redirect.github.com/nodejs/node/commit/5a5d73e4c5)] - **build**: pass target architecture to small-icu genccode (ulofiai) [#&#8203;65095](https://redirect.github.com/nodejs/node/pull/65095)
- \[[`273e72d1a5`](https://redirect.github.com/nodejs/node/commit/273e72d1a5)] - **build**: deprecate always enabled `--enable-static` (Chengzhong Wu) [#&#8203;65103](https://redirect.github.com/nodejs/node/pull/65103)
- \[[`89a67246e3`](https://redirect.github.com/nodejs/node/commit/89a67246e3)] - **build**: check FIPS option value in node.gyp (Filip Skokan) [#&#8203;64982](https://redirect.github.com/nodejs/node/pull/64982)
- \[[`2d21f41cd5`](https://redirect.github.com/nodejs/node/commit/2d21f41cd5)] - **build**: handle malformed OpenSSL macros (Filip Skokan) [#&#8203;64982](https://redirect.github.com/nodejs/node/pull/64982)
- \[[`f62bc0f862`](https://redirect.github.com/nodejs/node/commit/f62bc0f862)] - **build,win**: add PGO workload scripts (Stefan Stojanovic) [#&#8203;63696](https://redirect.github.com/nodejs/node/pull/63696)
- \[[`33d0c7dc12`](https://redirect.github.com/nodejs/node/commit/33d0c7dc12)] - **child\_process**: keep SIGWINCH from killing on Win (Kirill Saied) [#&#8203;64510](https://redirect.github.com/nodejs/node/pull/64510)
- \[[`71106e1f17`](https://redirect.github.com/nodejs/node/commit/71106e1f17)] - **crypto**: update root certificates to NSS 3.126 (Node.js GitHub Bot) [#&#8203;65495](https://redirect.github.com/nodejs/node/pull/65495)
- \[[`419af8b86d`](https://redirect.github.com/nodejs/node/commit/419af8b86d)] - **crypto**: fix missing error checks on ASN1\_STRING\_to\_UTF8() (Nora Dossche) [#&#8203;65200](https://redirect.github.com/nodejs/node/pull/65200)
- \[[`8029383f3f`](https://redirect.github.com/nodejs/node/commit/8029383f3f)] - **crypto**: use available BoringSSL APIs (Filip Skokan) [#&#8203;65423](https://redirect.github.com/nodejs/node/pull/65423)
- \[[`a9bd780e19`](https://redirect.github.com/nodejs/node/commit/a9bd780e19)] - **crypto**: remove obsolete BoringSSL shims (Filip Skokan) [#&#8203;65423](https://redirect.github.com/nodejs/node/pull/65423)
- \[[`7defefad3f`](https://redirect.github.com/nodejs/node/commit/7defefad3f)] - **crypto**: read WebCrypto inputs through primordials (Filip Skokan) [#&#8203;65115](https://redirect.github.com/nodejs/node/pull/65115)
- \[[`6ed1e38627`](https://redirect.github.com/nodejs/node/commit/6ed1e38627)] - **crypto**: fix disabling FIPS mode (Filip Skokan) [#&#8203;64982](https://redirect.github.com/nodejs/node/pull/64982)
- \[[`afca0a912d`](https://redirect.github.com/nodejs/node/commit/afca0a912d)] - **(SEMVER-MINOR)** **crypto**: support loading private keys through STORE loaders (Filip Skokan) [#&#8203;63949](https://redirect.github.com/nodejs/node/pull/63949)
- \[[`9b9dd6e9cf`](https://redirect.github.com/nodejs/node/commit/9b9dd6e9cf)] - **debugger**: wait for target startup (Filip Skokan) [#&#8203;65194](https://redirect.github.com/nodejs/node/pull/65194)
- \[[`07faaeeffd`](https://redirect.github.com/nodejs/node/commit/07faaeeffd)] - **deps**: update corepack to 0.36.0 (Node.js GitHub Bot) [#&#8203;65653](https://redirect.github.com/nodejs/node/pull/65653)
- \[[`53cba013c7`](https://redirect.github.com/nodejs/node/commit/53cba013c7)] - **deps**: update undici to 7.29.1 (Node.js GitHub Bot) [#&#8203;65789](https://redirect.github.com/nodejs/node/pull/65789)
- \[[`0268ca547c`](https://redirect.github.com/nodejs/node/commit/0268ca547c)] - **deps**: update archs files for openssl-3.5.8 (Node.js GitHub Bot) [#&#8203;65542](https://redirect.github.com/nodejs/node/pull/65542)
- \[[`6274fccbd9`](https://redirect.github.com/nodejs/node/commit/6274fccbd9)] - **deps**: upgrade openssl sources to openssl-3.5.8 (Node.js GitHub Bot) [#&#8203;65542](https://redirect.github.com/nodejs/node/pull/65542)
- \[[`6bdcd121fa`](https://redirect.github.com/nodejs/node/commit/6bdcd121fa)] - **deps**: update zlib to 1.3.2.1-motley-8002e91 (Node.js GitHub Bot) [#&#8203;65316](https://redirect.github.com/nodejs/node/pull/65316)
- \[[`c2aa446b6d`](https://redirect.github.com/nodejs/node/commit/c2aa446b6d)] - **deps**: update simdjson to 4.6.7 (Node.js GitHub Bot) [#&#8203;65318](https://redirect.github.com/nodejs/node/pull/65318)
- \[[`3e58e48ea8`](https://redirect.github.com/nodejs/node/commit/3e58e48ea8)] - **deps**: update googletest to [`49495ea`](https://redirect.github.com/nodejs/node/commit/49495eacfdbda3f4b6ba219923fedbb2e3f99376) (Node.js GitHub Bot) [#&#8203;65317](https://redirect.github.com/nodejs/node/pull/65317)
- \[[`670b3665c0`](https://redirect.github.com/nodejs/node/commit/670b3665c0)] - **deps**: cherry-pick [libuv/libuv@`e640dc9`](https://redirect.github.com/libuv/libuv/commit/e640dc9) (ulofiai) [#&#8203;65118](https://redirect.github.com/nodejs/node/pull/65118)
- \[[`ca1c67b021`](https://redirect.github.com/nodejs/node/commit/ca1c67b021)] - **deps**: float ICU-23262 patch for icu78 (René) [#&#8203;64678](https://redirect.github.com/nodejs/node/pull/64678)
- \[[`4ad043b0aa`](https://redirect.github.com/nodejs/node/commit/4ad043b0aa)] - **deps**: enable AVX-512 OpenSSL asm with clang (Daniel Lemire) [#&#8203;65136](https://redirect.github.com/nodejs/node/pull/65136)
- \[[`96b4af109b`](https://redirect.github.com/nodejs/node/commit/96b4af109b)] - **deps**: update googletest to [`d89aac5`](https://redirect.github.com/nodejs/node/commit/d89aac5f0dd4021198d903d39de16f896726de21) (Node.js GitHub Bot) [#&#8203;65153](https://redirect.github.com/nodejs/node/pull/65153)
- \[[`774f663c56`](https://redirect.github.com/nodejs/node/commit/774f663c56)] - **dgram**: don't swallow bind errors when callback is provided (armanmikoyan) [#&#8203;62602](https://redirect.github.com/nodejs/node/pull/62602)
- \[[`94b118d62e`](https://redirect.github.com/nodejs/node/commit/94b118d62e)] - **diagnostics\_channel**: validate before channel activation (Trivikram Kamat) [#&#8203;65313](https://redirect.github.com/nodejs/node/pull/65313)
- \[[`09788665bd`](https://redirect.github.com/nodejs/node/commit/09788665bd)] - **dns**: validate address type in lookupService (Lazizbek Ergashev) [#&#8203;64878](https://redirect.github.com/nodejs/node/pull/64878)
- \[[`15f95fc0e2`](https://redirect.github.com/nodejs/node/commit/15f95fc0e2)] - **dns**: validate port range in `setServers()` (René) [#&#8203;65021](https://redirect.github.com/nodejs/node/pull/65021)
- \[[`37b9e9a154`](https://redirect.github.com/nodejs/node/commit/37b9e9a154)] - **dns**: fix crash on setServers with port 0 (Lazizbek Ergashev) [#&#8203;65009](https://redirect.github.com/nodejs/node/pull/65009)
- \[[`cd6205fa0d`](https://redirect.github.com/nodejs/node/commit/cd6205fa0d)] - **doc**: update AHAFS reference link (Taeuk Ha) [#&#8203;65481](https://redirect.github.com/nodejs/node/pull/65481)
- \[[`0e6f9ae42e`](https://redirect.github.com/nodejs/node/commit/0e6f9ae42e)] - **doc**: fix property names in os.networkInterfaces() example (Jihwan) [#&#8203;65469](https://redirect.github.com/nodejs/node/pull/65469)
- \[[`034a827b41`](https://redirect.github.com/nodejs/node/commit/034a827b41)] - **doc**: fix broken links in cli.md (Donghoon Kang) [#&#8203;65412](https://redirect.github.com/nodejs/node/pull/65412)
- \[[`eb364621d4`](https://redirect.github.com/nodejs/node/commit/eb364621d4)] - **doc**: remove outdated WASI version fallback (이혜미) [#&#8203;65303](https://redirect.github.com/nodejs/node/pull/65303)
- \[[`b13f425bf8`](https://redirect.github.com/nodejs/node/commit/b13f425bf8)] - **doc**: fix broken GYP link in n-api.md (Donghoon Kang) [#&#8203;65413](https://redirect.github.com/nodejs/node/pull/65413)
- \[[`45c4011067`](https://redirect.github.com/nodejs/node/commit/45c4011067)] - **doc**: document that an empty OPENSSL\_CONF skips config loading (Orgad Shaneh) [#&#8203;64949](https://redirect.github.com/nodejs/node/pull/64949)
- \[[`fde6776c5f`](https://redirect.github.com/nodejs/node/commit/fde6776c5f)] - **doc**: fix broken TLS security level example (soreavis) [#&#8203;65391](https://redirect.github.com/nodejs/node/pull/65391)
- \[[`290c1fec04`](https://redirect.github.com/nodejs/node/commit/290c1fec04)] - **doc**: clarify socket destroyed behavior (Dayun) [#&#8203;65395](https://redirect.github.com/nodejs/node/pull/65395)
- \[[`a7e8269947`](https://redirect.github.com/nodejs/node/commit/a7e8269947)] - **doc**: update outdated nodejs.org guide links (Donghoon Kang) [#&#8203;65394](https://redirect.github.com/nodejs/node/pull/65394)
- \[[`7809f11249`](https://redirect.github.com/nodejs/node/commit/7809f11249)] - **doc**: clarify that ipv4 mapped to ipv6 are classified as ipv6 (Vedant Kulkarni) [#&#8203;62117](https://redirect.github.com/nodejs/node/pull/62117)
- \[[`64cd3a6e95`](https://redirect.github.com/nodejs/node/commit/64cd3a6e95)] - **doc**: clarify how fs.Dirent file types are determined (soreavis) [#&#8203;64532](https://redirect.github.com/nodejs/node/pull/64532)
- \[[`9b92fdce14`](https://redirect.github.com/nodejs/node/commit/9b92fdce14)] - **doc**: update security release prepare command (Rafael Gonzaga) [#&#8203;64699](https://redirect.github.com/nodejs/node/pull/64699)
- \[[`6f9b9df3c1`](https://redirect.github.com/nodejs/node/commit/6f9b9df3c1)] - **doc**: clarify copyFile symlink behavior (T) [#&#8203;62941](https://redirect.github.com/nodejs/node/pull/62941)
- \[[`2480acb550`](https://redirect.github.com/nodejs/node/commit/2480acb550)] - **doc**: document setRawMode write access on Windows (Erik Demaine) [#&#8203;63856](https://redirect.github.com/nodejs/node/pull/63856)
- \[[`a1e9c3a5db`](https://redirect.github.com/nodejs/node/commit/a1e9c3a5db)] - **doc**: add missing return types in fs.md (Chaseton Collins) [#&#8203;65307](https://redirect.github.com/nodejs/node/pull/65307)
- \[[`4533572040`](https://redirect.github.com/nodejs/node/commit/4533572040)] - **doc**: add missing return types in buffer.md (Yuya Inoue) [#&#8203;65308](https://redirect.github.com/nodejs/node/pull/65308)
- \[[`39ecedbbd2`](https://redirect.github.com/nodejs/node/commit/39ecedbbd2)] - **doc**: fix lint clean command (greenhead) [#&#8203;65274](https://redirect.github.com/nodejs/node/pull/65274)
- \[[`0b1fb8fcd8`](https://redirect.github.com/nodejs/node/commit/0b1fb8fcd8)] - **doc**: fix typo in onboarding.md (서울민트초코) [#&#8203;65295](https://redirect.github.com/nodejs/node/pull/65295)
- \[[`3165b5d38a`](https://redirect.github.com/nodejs/node/commit/3165b5d38a)] - **doc**: add missing `added:` tags to `fs.lchmod` (Lazizbek Ergashev) [#&#8203;65283](https://redirect.github.com/nodejs/node/pull/65283)
- \[[`113b808e59`](https://redirect.github.com/nodejs/node/commit/113b808e59)] - **doc**: fix SQLite changeset constant descriptions (greenhead) [#&#8203;65265](https://redirect.github.com/nodejs/node/pull/65265)
- \[[`c3eb51d5a1`](https://redirect.github.com/nodejs/node/commit/c3eb51d5a1)] - **doc**: document open pull request limit (Matteo Collina) [#&#8203;65250](https://redirect.github.com/nodejs/node/pull/65250)
- \[[`d7accdcd52`](https://redirect.github.com/nodejs/node/commit/d7accdcd52)] - **doc**: document http2 header constants (Harjoth Khara) [#&#8203;64548](https://redirect.github.com/nodejs/node/pull/64548)
- \[[`f47111416f`](https://redirect.github.com/nodejs/node/commit/f47111416f)] - **doc**: create ai-guidelines and include to CONTRIBUTING (Rafael Gonzaga) [#&#8203;62105](https://redirect.github.com/nodejs/node/pull/62105)
- \[[`c3b120e737`](https://redirect.github.com/nodejs/node/commit/c3b120e737)] - **doc**: update synopsis (Augustin Mauroy) [#&#8203;65171](https://redirect.github.com/nodejs/node/pull/65171)
- \[[`39f4c831fd`](https://redirect.github.com/nodejs/node/commit/39f4c831fd)] - **doc**: fix broken internal links (greenhead) [#&#8203;64901](https://redirect.github.com/nodejs/node/pull/64901)
- \[[`be25cdd69e`](https://redirect.github.com/nodejs/node/commit/be25cdd69e)] - **doc**: report proper return type on urlPattern.test (Brian Muenzenmeyer) [#&#8203;64831](https://redirect.github.com/nodejs/node/pull/64831)
- \[[`1bf7737810`](https://redirect.github.com/nodejs/node/commit/1bf7737810)] - **doc**: fix permission documentation examples (greenhead) [#&#8203;64897](https://redirect.github.com/nodejs/node/pull/64897)
- \[[`b7932e68a1`](https://redirect.github.com/nodejs/node/commit/b7932e68a1)] - **doc**: document sqlite parameter binding (Guilherme Araújo) [#&#8203;65089](https://redirect.github.com/nodejs/node/pull/65089)
- \[[`5234a5169c`](https://redirect.github.com/nodejs/node/commit/5234a5169c)] - **doc**: finalize statements in sqlite examples (Guilherme Araújo) [#&#8203;65088](https://redirect.github.com/nodejs/node/pull/65088)
- \[[`39ea929da7`](https://redirect.github.com/nodejs/node/commit/39ea929da7)] - **doc**: document quic stopSending() and resetStream() (Issac) [#&#8203;64888](https://redirect.github.com/nodejs/node/pull/64888)
- \[[`2ba198db73`](https://redirect.github.com/nodejs/node/commit/2ba198db73)] - **doc**: clarify sqlite bare parameter default (Sumit Kumar Das) [#&#8203;62009](https://redirect.github.com/nodejs/node/pull/62009)
- \[[`314f9b200f`](https://redirect.github.com/nodejs/node/commit/314f9b200f)] - **doc**: remove usage of `util.inherits` (Augustin Mauroy) [#&#8203;60817](https://redirect.github.com/nodejs/node/pull/60817)
- \[[`d82a61662c`](https://redirect.github.com/nodejs/node/commit/d82a61662c)] - **doc**: fix grammar in worker\_threads.md (이혜미) [#&#8203;64913](https://redirect.github.com/nodejs/node/pull/64913)
- \[[`44c0c8ff5b`](https://redirect.github.com/nodejs/node/commit/44c0c8ff5b)] - **doc**: clarify OpenSSL FIPS configuration (Filip Skokan) [#&#8203;64982](https://redirect.github.com/nodejs/node/pull/64982)
- \[[`9b2ca70e0d`](https://redirect.github.com/nodejs/node/commit/9b2ca70e0d)] - **doc**: remove `--expose-gc` flag from CLI documentation (Dario Piotrowicz) [#&#8203;58909](https://redirect.github.com/nodejs/node/pull/58909)
- \[[`a0a12397b9`](https://redirect.github.com/nodejs/node/commit/a0a12397b9)] - **doc**: document ArrayBuffer support in pbkd2Sync (kyungrae2002) [#&#8203;64976](https://redirect.github.com/nodejs/node/pull/64976)
- \[[`b48699e077`](https://redirect.github.com/nodejs/node/commit/b48699e077)] - **doc**: correct default highWaterMark values (Yilong Li) [#&#8203;64617](https://redirect.github.com/nodejs/node/pull/64617)
- \[[`0312ee133c`](https://redirect.github.com/nodejs/node/commit/0312ee133c)] - **esm**: avoid super-linear data URL MIME regex (Sumit Kumar Das) [#&#8203;61951](https://redirect.github.com/nodejs/node/pull/61951)
- \[[`cd84d55c81`](https://redirect.github.com/nodejs/node/commit/cd84d55c81)] - **esm**: only register text format when enabled (Efe Karasakal) [#&#8203;64992](https://redirect.github.com/nodejs/node/pull/64992)
- \[[`c0a8ef611e`](https://redirect.github.com/nodejs/node/commit/c0a8ef611e)] - **esm**: fix wasm import name in error message (이혜미) [#&#8203;64950](https://redirect.github.com/nodejs/node/pull/64950)
- \[[`e6c34f90c2`](https://redirect.github.com/nodejs/node/commit/e6c34f90c2)] - **events**: inline iterationCondition hybrid dispatch closure (Szymon Łągiewka) [#&#8203;64473](https://redirect.github.com/nodejs/node/pull/64473)
- \[[`6ee4b40c91`](https://redirect.github.com/nodejs/node/commit/6ee4b40c91)] - **events**: inline createEvent hybrid dispatch closure (Szymon Łągiewka) [#&#8203;64473](https://redirect.github.com/nodejs/node/pull/64473)
- \[[`367549eed5`](https://redirect.github.com/nodejs/node/commit/367549eed5)] - **fs**: use sized reads for large files in readFileUtf8 (Shelley Vohr) [#&#8203;65328](https://redirect.github.com/nodejs/node/pull/65328)
- \[[`8a5b1ae4c2`](https://redirect.github.com/nodejs/node/commit/8a5b1ae4c2)] - **fs**: fix realpath of namespaced drive paths (Jason Zhang) [#&#8203;65378](https://redirect.github.com/nodejs/node/pull/65378)
- \[[`c9233b950d`](https://redirect.github.com/nodejs/node/commit/c9233b950d)] - **fs**: fix glob early return skipping sibling entries (Srinu desetti) [#&#8203;64895](https://redirect.github.com/nodejs/node/pull/64895)
- \[[`bc54dd8905`](https://redirect.github.com/nodejs/node/commit/bc54dd8905)] - **fs**: pass symlink type in cp when filter is provided (Jerry Zhao) [#&#8203;62654](https://redirect.github.com/nodejs/node/pull/62654)
- \[[`fcb4333aca`](https://redirect.github.com/nodejs/node/commit/fcb4333aca)] - **fs**: allocate FSReqPromise stat arrays lazily (Samuel Attard) [#&#8203;63886](https://redirect.github.com/nodejs/node/pull/63886)
- \[[`c35876154e`](https://redirect.github.com/nodejs/node/commit/c35876154e)] - **fs**: fix out-of-bounds write in mkdtemp for long prefixes (Hierax\_Umbra) [#&#8203;64770](https://redirect.github.com/nodejs/node/pull/64770)
- \[[`b269616936`](https://redirect.github.com/nodejs/node/commit/b269616936)] - **fs**: treat `std::errc::permission_denied` as `EPERM` error (Kirill Saied) [#&#8203;64698](https://redirect.github.com/nodejs/node/pull/64698)
- \[[`212fe77e76`](https://redirect.github.com/nodejs/node/commit/212fe77e76)] - **fs**: add windowsHandle option to file streams (Kirill Saied) [#&#8203;63851](https://redirect.github.com/nodejs/node/pull/63851)
- \[[`75df6cb435`](https://redirect.github.com/nodejs/node/commit/75df6cb435)] - **http**: improve performance with known-length calls to end() (Tim Perry) [#&#8203;65466](https://redirect.github.com/nodejs/node/pull/65466)
- \[[`48d9cd4a28`](https://redirect.github.com/nodejs/node/commit/48d9cd4a28)] - **http**: cache maxHeaderPairs per header section (GetThatCookie) [#&#8203;64988](https://redirect.github.com/nodejs/node/pull/64988)
- \[[`04785c8f43`](https://redirect.github.com/nodejs/node/commit/04785c8f43)] - **http**: fix keylog listener setup on existing agent sockets (Shani Singh) [#&#8203;65066](https://redirect.github.com/nodejs/node/pull/65066)
- \[[`4b90031534`](https://redirect.github.com/nodejs/node/commit/4b90031534)] - **http**: emit drain on socket takeover and avoid stale HWM reuse (Naman Trivedi) [#&#8203;64991](https://redirect.github.com/nodejs/node/pull/64991)
- \[[`83a27559cd`](https://redirect.github.com/nodejs/node/commit/83a27559cd)] - **http2**: adapt receive deferral for Node.js 24 (Matteo Collina) [#&#8203;65093](https://redirect.github.com/nodejs/node/pull/65093)
- \[[`f43bed0ecc`](https://redirect.github.com/nodejs/node/commit/f43bed0ecc)] - **http2**: avoid uaf while receiving and sending rst\_stream (esgor) [#&#8203;64166](https://redirect.github.com/nodejs/node/pull/64166)
- \[[`b42d664321`](https://redirect.github.com/nodejs/node/commit/b42d664321)] - **inspector**: avoid calling into JS from V8 interrupts (Joyee Cheung) [#&#8203;65028](https://redirect.github.com/nodejs/node/pull/65028)
- \[[`6bf852197d`](https://redirect.github.com/nodejs/node/commit/6bf852197d)] - **lib**: use bracket notation instead of startsWith/endsWith for single char (Taejin Kim) [#&#8203;61500](https://redirect.github.com/nodejs/node/pull/61500)
- \[[`151ca7e104`](https://redirect.github.com/nodejs/node/commit/151ca7e104)] - **lib**: harden webidl dictionary member reads (Filip Skokan) [#&#8203;65115](https://redirect.github.com/nodejs/node/pull/65115)
- \[[`7e8c2c9f44`](https://redirect.github.com/nodejs/node/commit/7e8c2c9f44)] - **lib**: use validateArray for array arguments (greenhead) [#&#8203;64959](https://redirect.github.com/nodejs/node/pull/64959)
- \[[`424fe2bc5a`](https://redirect.github.com/nodejs/node/commit/424fe2bc5a)] - **lib**: add and test \[EnforceRange] in webcrypto dictionaries (Filip Skokan) [#&#8203;65091](https://redirect.github.com/nodejs/node/pull/65091)
- \[[`0529772798`](https://redirect.github.com/nodejs/node/commit/0529772798)] - **(SEMVER-MINOR)** **lib,src**: improve histogram implementation (James M Snell) [#&#8203;65024](https://redirect.github.com/nodejs/node/pull/65024)
- \[[`186e1b76e8`](https://redirect.github.com/nodejs/node/commit/186e1b76e8)] - **meta**: move targos to emeritus (Michaël Zasso) [#&#8203;65393](https://redirect.github.com/nodejs/node/pull/65393)
- \[[`b41b07c72a`](https://redirect.github.com/nodejs/node/commit/b41b07c72a)] - **meta**: add unified http api initiative (James M Snell) [#&#8203;65139](https://redirect.github.com/nodejs/node/pull/65139)
- \[[`f85b6ecd67`](https://redirect.github.com/nodejs/node/commit/f85b6ecd67)] - **meta**: move one or more collaborators to emeritus (Node.js GitHub Bot) [#&#8203;65182](https://redirect.github.com/nodejs/node/pull/65182)
- \[[`8f3d01bdce`](https://redirect.github.com/nodejs/node/commit/8f3d01bdce)] - **meta**: add Aviv Keller to `.mailmap` (Aviv Keller) [#&#8203;65048](https://redirect.github.com/nodejs/node/pull/65048)
- \[[`cfad1d5b28`](https://redirect.github.com/nodejs/node/commit/cfad1d5b28)] - **meta**: update sccache to 0.17.0 (René) [#&#8203;64985](https://redirect.github.com/nodejs/node/pull/64985)
- \[[`349c53c441`](https://redirect.github.com/nodejs/node/commit/349c53c441)] - **module**: report unreadable package.json (Paul Bouchon) [#&#8203;65223](https://redirect.github.com/nodejs/node/pull/65223)
- \[[`bd21e6706e`](https://redirect.github.com/nodejs/node/commit/bd21e6706e)] - **module**: cache nearest parent package.json per directory (Shelley Vohr) [#&#8203;65326](https://redirect.github.com/nodejs/node/pull/65326)
- \[[`961bd04370`](https://redirect.github.com/nodejs/node/commit/961bd04370)] - **module**: fix --check on ambiguous ESM files (Paul Bouchon) [#&#8203;65203](https://redirect.github.com/nodejs/node/pull/65203)
- \[[`f45ef73420`](https://redirect.github.com/nodejs/node/commit/f45ef73420)] - **net**: handle undefined parent in \_unrefTimer and \_destroy (Shivay-98) [#&#8203;64644](https://redirect.github.com/nodejs/node/pull/64644)
- \[[`41c7062b81`](https://redirect.github.com/nodejs/node/commit/41c7062b81)] - **(SEMVER-MINOR)** **net**: improve performance of net.BlockList (James M Snell) [#&#8203;64974](https://redirect.github.com/nodejs/node/pull/64974)
- \[[`5197b5a3c5`](https://redirect.github.com/nodejs/node/commit/5197b5a3c5)] - **(SEMVER-MINOR)** **perf\_hooks**: add statistical hypothesis testing to histogram (James M Snell) [#&#8203;65416](https://redirect.github.com/nodejs/node/pull/65416)
- \[[`1722ddac28`](https://redirect.github.com/nodejs/node/commit/1722ddac28)] - **permission**: enforce addon permission in GetLinkedBinding (Rafael Gonzaga) [#&#8203;65432](https://redirect.github.com/nodejs/node/pull/65432)
- \[[`dff2b675db`](https://redirect.github.com/nodejs/node/commit/dff2b675db)] - **process**: validate resource stats array offsets (Archkon) [#&#8203;65098](https://redirect.github.com/nodejs/node/pull/65098)
- \[[`f277983e7b`](https://redirect.github.com/nodejs/node/commit/f277983e7b)] - **quic**: changes for nghttp3\_conn\_close\_stream2 (Marten Richter) [#&#8203;64574](https://redirect.github.com/nodejs/node/pull/64574)
- \[[`a4c770c78e`](https://redirect.github.com/nodejs/node/commit/a4c770c78e)] - **quic**: mark drain promise handled (James M Snell) [#&#8203;65319](https://redirect.github.com/nodejs/node/pull/65319)
- \[[`2460b171c5`](https://redirect.github.com/nodejs/node/commit/2460b171c5)] - **quic**: reset rejected HTTP/3 request streams with H3\_REQUEST\_REJECTED (trivenay) [#&#8203;65442](https://redirect.github.com/nodejs/node/pull/65442)
- \[[`18a7ccf302`](https://redirect.github.com/nodejs/node/commit/18a7ccf302)] - **quic**: write desired size needs update on maxstream (Marten Richter) [#&#8203;64768](https://redirect.github.com/nodejs/node/pull/64768)
- \[[`9017f4a780`](https://redirect.github.com/nodejs/node/commit/9017f4a780)] - **quic**: do not destroy incoming streams that have a consumer (trivenay) [#&#8203;65335](https://redirect.github.com/nodejs/node/pull/65335)
- \[[`ddc41c1ef4`](https://redirect.github.com/nodejs/node/commit/ddc41c1ef4)] - **quic**: fix wake up blob (Marten Richter) [#&#8203;64044](https://redirect.github.com/nodejs/node/pull/64044)
- \[[`88bee43d7c`](https://redirect.github.com/nodejs/node/commit/88bee43d7c)] - **quic**: convert incoming :status header to number (Hallison Pereira Melo) [#&#8203;63589](https://redirect.github.com/nodejs/node/pull/63589)
- \[[`cd776fe97c`](https://redirect.github.com/nodejs/node/commit/cd776fe97c)] - **quic**: fix infinite loop if STOP\_SENDING received on a buffering stream (Tim Perry) [#&#8203;64715](https://redirect.github.com/nodejs/node/pull/64715)
- \[[`4f6eda3c23`](https://redirect.github.com/nodejs/node/commit/4f6eda3c23)] - **repl**: keep entries added while history file is loading (Mhayk Whandson) [#&#8203;64513](https://redirect.github.com/nodejs/node/pull/64513)
- \[[`cd1e6ce29b`](https://redirect.github.com/nodejs/node/commit/cd1e6ce29b)] - **repl**: add benchmarks (Aviv Keller) [#&#8203;64590](https://redirect.github.com/nodejs/node/pull/64590)
- \[[`2ba740669d`](https://redirect.github.com/nodejs/node/commit/2ba740669d)] - **sea**: avoid dangling CLI option pointers (Archkon) [#&#8203;64755](https://redirect.github.com/nodejs/node/pull/64755)
- \[[`ec5e2d6856`](https://redirect.github.com/nodejs/node/commit/ec5e2d6856)] - **sea**: handle NUL bytes in asset keys (Archkon) [#&#8203;64773](https://redirect.github.com/nodejs/node/pull/64773)
- \[[`703b854293`](https://redirect.github.com/nodejs/node/commit/703b854293)] - **sea**: reject trailing content in config JSON (Archkon) [#&#8203;64774](https://redirect.github.com/nodejs/node/pull/64774)
- \[[`a61a5fdd1c`](https://redirect.github.com/nodejs/node/commit/a61a5fdd1c)] - **sqlite**: prevent reentrant session.close() (Trivikram Kamat) [#&#8203;65349](https://redirect.github.com/nodejs/node/pull/65349)
- \[[`6ae81be0a3`](https://redirect.github.com/nodejs/node/commit/6ae81be0a3)] - **sqlite**: reject statement-less SQL in prepare() (Trevor Burnham) [#&#8203;65157](https://redirect.github.com/nodejs/node/pull/65157)
- \[[`043dfe4996`](https://redirect.github.com/nodejs/node/commit/043dfe4996)] - **sqlite**: reject statement-less SQL in SQLTagStore (Trevor Burnham) [#&#8203;65157](https://redirect.github.com/nodejs/node/pull/65157)
- \[[`b8faee02e1`](https://redirect.github.com/nodejs/node/commit/b8faee02e1)] - **sqlite**: check null returns from sqlite value functions (Nora Dossche) [#&#8203;63288](https://redirect.github.com/nodejs/node/pull/63288)
- \[[`d6d2a71bee`](https://redirect.github.com/nodejs/node/commit/d6d2a71bee)] - **sqlite**: validate maxSize argument in createTagStore() (Anshika Jain) [#&#8203;63792](https://redirect.github.com/nodejs/node/pull/63792)
- \[[`61a046309f`](https://redirect.github.com/nodejs/node/commit/61a046309f)] - **sqlite**: reject non-positive backup rates (Trivikram Kamat) [#&#8203;64893](https://redirect.github.com/nodejs/node/pull/64893)
- \[[`514e3f30fb`](https://redirect.github.com/nodejs/node/commit/514e3f30fb)] - **sqlite**: clear SQLTagStore bindings (Matteo Collina) [#&#8203;65041](https://redirect.github.com/nodejs/node/pull/65041)
- \[[`c1542255b8`](https://redirect.github.com/nodejs/node/commit/c1542255b8)] - **sqlite**: bind Boolean (mike-git374) [#&#8203;62001](https://redirect.github.com/nodejs/node/pull/62001)
- \[[`cdb732beb5`](https://redirect.github.com/nodejs/node/commit/cdb732beb5)] - **sqlite**: fix undefined behaviour in `Session::Changeset()` (Nora Dossche) [#&#8203;63637](https://redirect.github.com/nodejs/node/pull/63637)
- \[[`fbe8861111`](https://redirect.github.com/nodejs/node/commit/fbe8861111)] - **sqlite**: bind ArrayBuffer (mike-git374) [#&#8203;62061](https://redirect.github.com/nodejs/node/pull/62061)
- \[[`e3c6bd6bc8`](https://redirect.github.com/nodejs/node/commit/e3c6bd6bc8)] - **src**: add missing vector include (Filip Skokan) [#&#8203;65622](https://redirect.github.com/nodejs/node/pull/65622)
- \[[`793cf69df8`](https://redirect.github.com/nodejs/node/commit/793cf69df8)] - **src**: fix heap value deduplication in embedder graph (Ilyas Shabi) [#&#8203;64801](https://redirect.github.com/nodejs/node/pull/64801)
- \[[`ea5935b04c`](https://redirect.github.com/nodejs/node/commit/ea5935b04c)] - **src**: fix out-of-bounds write when transcoding odd-length ucs2 (nashit hayat) [#&#8203;64512](https://redirect.github.com/nodejs/node/pull/64512)
- \[[`22e5023f4d`](https://redirect.github.com/nodejs/node/commit/22e5023f4d)] - **src**: escape Windows environment variables in task runner (Antoine du Hamel) [#&#8203;65217](https://redirect.github.com/nodejs/node/pull/65217)
- \[[`a0f3c62bd9`](https://redirect.github.com/nodejs/node/commit/a0f3c62bd9)] - **src**: simplify c++ diagnostics channel API (James M Snell) [#&#8203;65158](https://redirect.github.com/nodejs/node/pull/65158)
- \[[`8e831a3d2e`](https://redirect.github.com/nodejs/node/commit/8e831a3d2e)] - **src**: make minor cleanup to permission checks (James M Snell) [#&#8203;65158](https://redirect.github.com/nodejs/node/pull/65158)
- \[[`968b2ca3a3`](https://redirect.github.com/nodejs/node/commit/968b2ca3a3)] - **src**: use DictionaryTemplate for permission diag channel message (James M Snell) [#&#8203;65158](https://redirect.github.com/nodejs/node/pull/65158)
- \[[`18e16e7f8d`](https://redirect.github.com/nodejs/node/commit/18e16e7f8d)] - **src**: cache permission strings (James M Snell) [#&#8203;65158](https://redirect.github.com/nodejs/node/pull/65158)
- \[[`c8625a4b6f`](https://redirect.github.com/nodejs/node/commit/c8625a4b6f)] - **src**: add SetAbortHandler (Max H Fisher) [#&#8203;64684](https://redirect.github.com/nodejs/node/pull/64684)
- \[[`c990140d60`](https://redirect.github.com/nodejs/node/commit/c990140d60)] - **src**: match cmd.exe case-insensitively in task runner (Archkon) [#&#8203;64907](https://redirect.github.com/nodejs/node/pull/64907)
- \[[`d7463b9dbc`](https://redirect.github.com/nodejs/node/commit/d7463b9dbc)] - **src**: reuse cached env strings in remaining files (Seongeun Lee) [#&#8203;65039](https://redirect.github.com/nodejs/node/pull/65039)
- \[[`b055a43b93`](https://redirect.github.com/nodejs/node/commit/b055a43b93)] - **src**: expose Windows-only fs open flags (Kirill Saied) [#&#8203;64775](https://redirect.github.com/nodejs/node/pull/64775)
- \[[`7f21e37496`](https://redirect.github.com/nodejs/node/commit/7f21e37496)] - **src**: report why --enable-fips failed (Filip Skokan) [#&#8203;64979](https://redirect.github.com/nodejs/node/pull/64979)
- \[[`8c11beae27`](https://redirect.github.com/nodejs/node/commit/8c11beae27)] - **src**: update repeated use strings to env (James M Snell) [#&#8203;64760](https://redirect.github.com/nodejs/node/pull/64760)
- \[[`9e2c477e26`](https://redirect.github.com/nodejs/node/commit/9e2c477e26)] - **stream**: normalize fused stateless transform results (Trivikram Kamat) [#&#8203;65367](https://redirect.github.com/nodejs/node/pull/65367)
- \[[`107e96dd41`](https://redirect.github.com/nodejs/node/commit/107e96dd41)] - **stream**: encode whole chunks in TextEncoderStream (Matteo Collina) [#&#8203;65414](https://redirect.github.com/nodejs/node/pull/65414)
- \[[`164068279b`](https://redirect.github.com/nodejs/node/commit/164068279b)] - **stream**: prevent share from eagerly draining source (Trivikram Kamat) [#&#8203;65338](https://redirect.github.com/nodejs/node/pull/65338)
- \[[`93d822bdc1`](https://redirect.github.com/nodejs/node/commit/93d822bdc1)] - **stream**: drain pending writes before broadcast end (Trivikram Kamat) [#&#8203;65334](https://redirect.github.com/nodejs/node/pull/65334)
- \[[`6b8b9c362f`](https://redirect.github.com/nodejs/node/commit/6b8b9c362f)] - **stream**: reuse unexposed managed read buffers (GetThatCookie) [#&#8203;64990](https://redirect.github.com/nodejs/node/pull/64990)
- \[[`4ec4fab367`](https://redirect.github.com/nodejs/node/commit/4ec4fab367)] - **stream**: avoid duplicated endReadableNT scheduling (Matteo Collina) [#&#8203;65310](https://redirect.github.com/nodejs/node/pull/65310)
- \[[`86d1196ebf`](https://redirect.github.com/nodejs/node/commit/86d1196ebf)] - **stream**: decouple transform backpressure changes (Matteo Collina) [#&#8203;65143](https://redirect.github.com/nodejs/node/pull/65143)
- \[[`b8a7a75b19`](https://redirect.github.com/nodejs/node/commit/b8a7a75b19)] - **stream**: reject pull on signal abort during flush (Trivikram Kamat) [#&#8203;65346](https://redirect.github.com/nodejs/node/pull/65346)
- \[[`386ed6a06d`](https://redirect.github.com/nodejs/node/commit/386ed6a06d)] - **stream**: avoid leaking consumers on signal failure (Trivikram Kamat) [#&#8203;65299](https://redirect.github.com/nodejs/node/pull/65299)
- \[[`74d53bd73b`](https://redirect.github.com/nodejs/node/commit/74d53bd73b)] - **stream**: use validateObject for zlib/iter params (greenhead) [#&#8203;65015](https://redirect.github.com/nodejs/node/pull/65015)
- \[[`3a174ce16b`](https://redirect.github.com/nodejs/node/commit/3a174ce16b)] - **stream**: use validateNumber for BYOB reader options.min (greenhead) [#&#8203;65014](https://redirect.github.com/nodejs/node/pull/65014)
- \[[`859ea01cb2`](https://redirect.github.com/nodejs/node/commit/859ea01cb2)] - **stream**: consolidate non-op algorithm callbacks (Matteo Collina) [#&#8203;65138](https://redirect.github.com/nodejs/node/pull/65138)
- \[[`c577669825`](https://redirect.github.com/nodejs/node/commit/c577669825)] - **stream**: cut promise churn in webstreams hot paths (Matteo Collina) [#&#8203;65138](https://redirect.github.com/nodejs/node/pull/65138)
- \[[`d631e910db`](https://redirect.github.com/nodejs/node/commit/d631e910db)] - **stream**: preserve falsy cancellation reasons (Trivikram Kamat) [#&#8203;64705](https://redirect.github.com/nodejs/node/pull/64705)
- \[[`f9c21eabbd`](https://redirect.github.com/nodejs/node/commit/f9c21eabbd)] - **stream**: use validateBuffer for BYOB reader view (greenhead) [#&#8203;65046](https://redirect.github.com/nodejs/node/pull/65046)
- \[[`b89c8f5d1e`](https://redirect.github.com/nodejs/node/commit/b89c8f5d1e)] - **stream**: fix recursive WritableStream abort (Jeong SeokChan) [#&#8203;64825](https://redirect.github.com/nodejs/node/pull/64825)
- \[[`39e0457e86`](https://redirect.github.com/nodejs/node/commit/39e0457e86)] - **test**: fix link-local dgram scope assertion (Filip Skokan) [#&#8203;65629](https://redirect.github.com/nodejs/node/pull/65629)
- \[[`1433cf9d5b`](https://redirect.github.com/nodejs/node/commit/1433cf9d5b)] - **test**: account for varied OpenSSL CCM final behaviours (Filip Skokan) [#&#8203;65542](https://redirect.github.com/nodejs/node/pull/65542)
- \[[`7d135d24b7`](https://redirect.github.com/nodejs/node/commit/7d135d24b7)] - **test**: convert forEach to for of test-messageevent-brandcheck file (Nachiketa Pathak) [#&#8203;65279](https://redirect.github.com/nodejs/node/pull/65279)
- \[[`421ee11715`](https://redirect.github.com/nodejs/node/commit/421ee11715)] - **test**: use spawnSyncAndAssert in windowsHide test (Junsoo Ha) [#&#8203;65351](https://redirect.github.com/nodejs/node/pull/65351)
- \[[`929d5705bc`](https://redirect.github.com/nodejs/node/commit/929d5705bc)] - **test**: remove test-debugger-run-after-quit-restart as flaky on macOS (Yuya Inoue) [#&#8203;65424](https://redirect.github.com/nodejs/node/pull/65424)
- \[[`f38f154c14`](https://redirect.github.com/nodejs/node/commit/f38f154c14)] - **test**: simplify test-timers-interval-promisified.js (Donghoon Kang) [#&#8203;65322](https://redirect.github.com/nodejs/node/pull/65322)
- \[[`a98e27f2a9`](https://redirect.github.com/nodejs/node/commit/a98e27f2a9)] - **test**: add Headers coverage and benchmark (Yagiz Nizipli) [#&#8203;65365](https://redirect.github.com/nodejs/node/pull/65365)
- \[[`38fdbb62aa`](https://redirect.github.com/nodejs/node/commit/38fdbb62aa)] - **test**: deflake test-net-listen-ipv6only (sangwook) [#&#8203;64173](https://redirect.github.com/nodejs/node/pull/64173)
- \[[`06c7684b01`](https://redirect.github.com/nodejs/node/commit/06c7684b01)] - **test**: use common/child\_process spawnSync helpers (Junsoo Ha) [#&#8203;65377](https://redirect.github.com/nodejs/node/pull/65377)
- \[[`6438c70004`](https://redirect.github.com/nodejs/node/commit/6438c70004)] - **test**: fix Linux debug skip in SEA test guard (구현우) [#&#8203;63751](https://redirect.github.com/nodejs/node/pull/63751)
- \[[`fe0e4f1b65`](https://redirect.github.com/nodejs/node/commit/fe0e4f1b65)] - **test**: avoid timer race in event loop delay test (Trivikram Kamat) [#&#8203;64728](https://redirect.github.com/nodejs/node/pull/64728)
- \[[`6ff69baea8`](https://redirect.github.com/nodejs/node/commit/6ff69baea8)] - **test**: enforce exit code in `test-http-server-stale-close` (Antoine du Hamel) [#&#8203;65198](https://redirect.github.com/nodejs/node/pull/65198)
- \[[`ba87603016`](https://redirect.github.com/nodejs/node/commit/ba87603016)] - **test**: convert test-async-local-storage-bind to async loop (freida-code) [#&#8203;65270](https://redirect.github.com/nodejs/node/pull/65270)
- \[[`8d6b89f454`](https://redirect.github.com/nodejs/node/commit/8d6b89f454)] - **test**: replace `forEach()` with `for...of` in parallel tests (Phillip Markert) [#&#8203;65272](https://redirect.github.com/nodejs/node/pull/65272)
- \[[`a60572a7bb`](https://redirect.github.com/nodejs/node/commit/a60572a7bb)] - **test**: convert forEach to for in test-constant.js file (NIxxy25) [#&#8203;65271](https://redirect.github.com/nodejs/node/pull/65271)
- \[[`f82060c6ce`](https://redirect.github.com/nodejs/node/commit/f82060c6ce)] - **test**: use for-of instead of forEach (Felix P.) [#&#8203;65268](https://redirect.github.com/nodejs/node/pull/65268)
- \[[`5ded71f9cc`](https://redirect.github.com/nodejs/node/commit/5ded71f9cc)] - **test**: cover `realpathSync` resolving symlinks after a FIFO stat (Hendrik Liebau) [#&#8203;65113](https://redirect.github.com/nodejs/node/pull/65113)
- \[[`ac9835225e`](https://redirect.github.com/nodejs/node/commit/ac9835225e)] - **test**: account for \[EnforceRange] in test-webcrypto-prototype-pollution (Filip Skokan) [#&#8203;65173](https://redirect.github.com/nodejs/node/pull/65173)
- \[[`550d24277e`](https://redirect.github.com/nodejs/node/commit/550d24277e)] - **test**: update WPT for WebCryptoAPI to [`4c2fd05`](https://redirect.github.com/nodejs/node/commit/4c2fd05ed5) (Node.js GitHub Bot) [#&#8203;65150](https://redirect.github.com/nodejs/node/pull/65150)
- \[[`2aa26559f0`](https://redirect.github.com/nodejs/node/commit/2aa26559f0)] - **test**: update WPT for urlpattern to [`4832db4`](https://redirect.github.com/nodejs/node/commit/4832db4761) (Node.js GitHub Bot) [#&#8203;65151](https://redirect.github.com/nodejs/node/pull/65151)
- \[[`d45c010108`](https://redirect.github.com/nodejs/node/commit/d45c010108)] - **test**: fix hidden error in test-http-server-stale-close.js (Meghan Denny) [#&#8203;59357](https://redirect.github.com/nodejs/node/pull/59357)
- \[[`881f8d092d`](https://redirect.github.com/nodejs/node/commit/881f8d092d)] - **test**: avoid deadlock issue in pipeline http2 tests to fix flakiness (Tim Perry) [#&#8203;65079](https://redirect.github.com/nodejs/node/pull/65079)
- \[[`e4b1e3ee75`](https://redirect.github.com/nodejs/node/commit/e4b1e3ee75)] - **test**: allow half-open CONNECT tunnel sockets (Trivikram Kamat) [#&#8203;64973](https://redirect.github.com/nodejs/node/pull/64973)
- \[[`00f4240d48`](https://redirect.github.com/nodejs/node/commit/00f4240d48)] - **test**: update passphrases to comply with the next OpenSSL FIPS mode (Filip Skokan) [#&#8203;65077](https://redirect.github.com/nodejs/node/pull/65077)
- \[[`cf7680efb7`](https://redirect.github.com/nodejs/node/commit/cf7680efb7)] - **test**: use libuv clock for immediate queue test (Trivikram Kamat) [#&#8203;64889](https://redirect.github.com/nodejs/node/pull/64889)
- \[[`b0c12772ff`](https://redirect.github.com/nodejs/node/commit/b0c12772ff)] - **test**: increase timeout in probe-failure-hang-during-evaluate (Joyee Cheung) [#&#8203;64719](https://redirect.github.com/nodejs/node/pull/64719)
- \[[`7e279104b4`](https://redirect.github.com/nodejs/node/commit/7e279104b4)] - **test**: update WPT for WebCryptoAPI to [`82c3d90`](https://redirect.github.com/nodejs/node/commit/82c3d9069c) (Node.js GitHub Bot) [#&#8203;64977](https://redirect.github.com/nodejs/node/pull/64977)
- \[[`ac11f88d16`](https://redirect.github.com/nodejs/node/commit/ac11f88d16)] - **test,doc**: cover and document multi-byte offset/size in randomFill (kyungrae2002) [#&#8203;64834](https://redirect.github.com/nodejs/node/pull/64834)
- \[[`67da38cada`](https://redirect.github.com/nodejs/node/commit/67da38cada)] - **test\_runner**: match dotfiles in default coverage exclude (semimikoh) [#&#8203;63401](https://redirect.github.com/nodejs/node/pull/63401)
- \[[`b06c61a08f`](https://redirect.github.com/nodejs/node/commit/b06c61a08f)] - **test\_runner**: print coverage and diagnostic info with dot reporter (mag123c) [#&#8203;61423](https://redirect.github.com/nodejs/node/pull/61423)
- \[[`7cb9c9c126`](https://redirect.github.com/nodejs/node/commit/7cb9c9c126)] - **test\_runner**: use run options with isolation="none" (Sylvester Keil) [#&#8203;62269](https://redirect.github.com/nodejs/node/pull/62269)
- \[[`339acf4201`](https://redirect.github.com/nodejs/node/commit/339acf4201)] - **test\_runner**: mock dual-package with conditional exports (Maruthan G) [#&#8203;62943](https://redirect.github.com/nodejs/node/pull/62943)
- \[[`e7a68bca08`](https://redirect.github.com/nodejs/node/commit/e7a68bca08)] - **test\_runner**: add classname hierarchy for JUnit reporter (mag123c) [#&#8203;60220](https://redirect.github.com/nodejs/node/pull/60220)
- \[[`6a248acefe`](https://redirect.github.com/nodejs/node/commit/6a248acefe)] - **test\_runner**: fix junit report on empty diagnostic (Lazizbek Ergashev) [#&#8203;65357](https://redirect.github.com/nodejs/node/pull/65357)
- \[[`d01dda79b6`](https://redirect.github.com/nodejs/node/commit/d01dda79b6)] - **test\_runner**: do not tag-filter test file wrappers (Chemi Atlow) [#&#8203;65170](https://redirect.github.com/nodejs/node/pull/65170)
- \[[`32ead10ddd`](https://redirect.github.com/nodejs/node/commit/32ead10ddd)] - **test\_runner**: fix env option validation (Jihwan) [#&#8203;64865](https://redirect.github.com/nodejs/node/pull/64865)
- \[[`b0ef155440`](https://redirect.github.com/nodejs/node/commit/b0ef155440)] - **tls**: throw on invalid ALPNProtocols instead of aborting (Sankalp Thakur) [#&#8203;65076](https://redirect.github.com/nodejs/node/pull/65076)
- \[[`dcf65c50ce`](https://redirect.github.com/nodejs/node/commit/dcf65c50ce)] - **tls**: fix authorized state on no-cert TLS1.3 client cert resumption (Tim Perry) [#&#8203;64677](https://redirect.github.com/nodejs/node/pull/64677)
- \[[`b0f54bda78`](https://redirect.github.com/nodejs/node/commit/b0f54bda78)] - **tools**: improve commit queue failure comment (Filip Skokan) [#&#8203;65433](https://redirect.github.com/nodejs/node/pull/65433)
- \[[`bc1f2718d5`](https://redirect.github.com/nodejs/node/commit/bc1f2718d5)] - **tools**: fix max body length handler in `create-release-proposal.sh` (Antoine du Hamel) [#&#8203;65455](https://redirect.github.com/nodejs/node/pull/65455)
- \[[`bbe76516a9`](https://redirect.github.com/nodejs/node/commit/bbe76516a9)] - **tools**: bump brace-expansion in `/tools/clang-format` (dependabot\[bot]) [#&#8203;64984](https://redirect.github.com/nodejs/node/pull/64984)
- \[[`67697debdf`](https://redirect.github.com/nodejs/node/commit/67697debdf)] - **tools**: make env variables consistent in cron jobs (Antoine du Hamel) [#&#8203;65168](https://redirect.github.com/nodejs/node/pull/65168)
- \[[`ac8a68ec92`](https://redirect.github.com/nodejs/node/commit/ac8a68ec92)] - **tools**: only include fast-tracked and old enough PRs in CQ (Antoine du Hamel) [#&#8203;65197](https://redirect.github.com/nodejs/node/pull/65197)
- \[[`6d9999fa2e`](https://redirect.github.com/nodejs/node/commit/6d9999fa2e)] - **tools**: remove skip logic in `commit-queue.sh` (Antoine du Hamel) [#&#8203;65162](https://redirect.github.com/nodejs/node/pull/65162)
- \[[`60bfa1694e`](https://redirect.github.com/nodejs/node/commit/60bfa1694e)] - **tools**: bump js-yaml from 4.2.0 to 4.3.1 in /tools/lint-md (dependabot\[bot]) [#&#8203;65129](https://redirect.github.com/nodejs/node/pull/65129)
- \[[`782748527e`](https://redirect.github.com/nodejs/node/commit/782748527e)] - **tools**: bump js-yaml from 4.2.0 to 4.3.1 in /tools/eslint (dependabot\[bot]) [#&#8203;65130](https://redirect.github.com/nodejs/node/pull/65130)
- \[[`dac257340f`](https://redirect.github.com/nodejs/node/commit/dac257340f)] - **tools**: fix GITHUB\_TOKEN permissions for CQ workflow (Antoine du Hamel) [#&#8203;65192](https://redirect.github.com/nodejs/node/pull/65192)
- \[[`e624785846`](https://redirect.github.com/nodejs/node/commit/e624785846)] - **tools**: use the read-only token when filtering PRs in CQ (Antoine du Hamel) [#&#8203;65169](https://redirect.github.com/nodejs/node/pull/65169)
- \[[`7d9dcfaaa7`](https://redirect.github.com/nodejs/node/commit/7d9dcfaaa7)] - **tools**: delay removal of `commit-queue` label (Antoine du Hamel) [#&#8203;65101](https://redirect.github.com/nodejs/node/pull/65101)
- \[[`0d7c7936e7`](https://redirect.github.com/nodejs/node/commit/0d7c7936e7)] - **tools**: move ncu config to global for commit queue (Filip Skokan) [#&#8203;65132](https://redirect.github.com/nodejs/node/pull/65132)
- \[[`5e1db5a38a`](https://redirect.github.com/nodejs/node/commit/5e1db5a38a)] - **tools**: prefilter commit queue metadata (Filip Skokan) [#&#8203;64343](https://redirect.github.com/nodejs/node/pull/64343)
- \[[`f41509b91d`](https://redirect.github.com/nodejs/node/commit/f41509b91d)] - **tools**: lazy-abort failed PR merges in CQ (Antoine du Hamel) [#&#8203;65004](https://redirect.github.com/nodejs/node/pull/65004)
- \[[`ceb0e99acd`](https://redirect.github.com/nodejs/node/commit/ceb0e99acd)] - **tools**: sync mk-ca-bundle.pl with curl (Archkon) [#&#8203;64753](https://redirect.github.com/nodejs/node/pull/64753)
- \[[`600663b23f`](https://redirect.github.com/nodejs/node/commit/600663b23f)] - **tty**: add raw-vt and io raw modes (Samuel Williams) [#&#8203;64140](https://redirect.github.com/nodejs/node/pull/64140)
- \[[`a40bfc742e`](https://redirect.github.com/nodejs/node/commit/a40bfc742e)] - **typings**: add signal\_wrap internal binding types (Seongeun Lee) [#&#8203;65229](https://redirect.github.com/nodejs/node/pull/65229)
- \[[`b103a4a3b9`](https://redirect.github.com/nodejs/node/commit/b103a4a3b9)] - **typings**: add diagnostics\_channel typings (Seongeun Lee) [#&#8203;65227](https://redirect.github.com/nodejs/node/pull/65227)
- \[[`e64de34b89`](https://redirect.github.com/nodejs/node/commit/e64de34b89)] - **typings**: add watchdog internal binding types (Seongeun Lee) [#&#8203;65228](https://redirect.github.com/nodejs/node/pull/65228)
- \[[`c57c83b4d1`](https://redirect.github.com/nodejs/node/commit/c57c83b4d1)] - **typings**: add internal\_only\_v8 binding typeis (Donghoon Kang) [#&#8203;65071](https://redirect.github.com/nodejs/node/pull/65071)
- \[[`f15e8c9dcd`](https://redirect.github.com/nodejs/node/commit/f15e8c9dcd)] - **typings**: add credentials internal binding types (Donghoon Kang) [#&#8203;65036](https://redirect.github.com/nodejs/node/pull/65036)
- \[[`a500256b0b`](https://redirect.github.com/nodejs/node/commit/a500256b0b)] - **url**: skip unused href reuse comparison (Yagiz Nizipli) [#&#8203;65361](https://redirect.github.com/nodejs/node/pull/65361)
- \[[`58390f8bec`](https://redirect.github.com/nodejs/node/commit/58390f8bec)] - **url**: speed up WHATWG URL parsing (Yagiz Nizipli) [#&#8203;65361](https://redirect.github.com/nodejs/node/pull/65361)
- \[[`7d5428c812`](https://redirect.github.com/nodejs/node/commit/7d5428c812)] - **url**: speed up URLSearchParams (Yagiz Nizipli) [#&#8203;65363](https://redirect.github.com/nodejs/node/pull/65363)
- \[[`8e2461d819`](https://redirect.github.com/nodejs/node/commit/8e2461d819)] - **url**: bounds-check short Windows file URL paths (Archkon) [#&#8203;64788](https://redirect.github.com/nodejs/node/pull/64788)
- \[[`9ffc0da90c`](https://redirect.github.com/nodejs/node/commit/9ffc0da90c)] - **url**: handle unparsable serialized URLs in setters (Matteo Collina) [#&#8203;64651](https://redirect.github.com/nodejs/node/pull/64651)
- \[[`fa9d4e075d`](https://redirect.github.com/nodejs/node/commit/fa9d4e075d)] - **util**: allow single-line format when break length is infinite (Hamid Reza Ghavami) [#&#8203;64238](https://redirect.github.com/nodejs/node/pull/64238)
- \[[`b68a7c3862`](https://redirect.github.com/nodejs/node/commit/b68a7c3862)] - **util**: fix OSC 8 hyperlink stripping in stripVTControlCharacters (Dushyant Singh Hada) [#&#8203;64319](https://redirect.github.com/nodejs/node/pull/64319)
- \[[`10cbb6dc00`](https://redirect.github.com/nodejs/node/commit/10cbb6dc00)] - **util**: fix formatting of functions returned from getters (Richard Gibson) [#&#8203;64839](https://redirect.github.com/nodejs/node/pull/64839)
- \[[`64c20b449e`](https://redirect.github.com/nodejs/node/commit/64c20b449e)] - **util**: use more primordials in `comparisons.js` (Ayoub Mabrouk) [#&#8203;61198](https://redirect.github.com/nodejs/node/pull/61198)
- \[[`cea9786de8`](https://redirect.github.com/nodejs/node/commit/cea9786de8)] - **util**: preserve function names without source map names (Hiroki Osame) [#&#8203;65108](https://redirect.github.com/nodejs/node/pull/65108)
- \[[`35c635b032`](https://redirect.github.com/nodejs/node/commit/35c635b032)] - **(SEMVER-MINOR)** **util**: add non-throwing MIMEType.parse (James M Snell) [#&#8203;64965](https://redirect.github.com/nodejs/node/pull/64965)
- \[[`5858c2ba9a`](https://redirect.github.com/nodejs/node/commit/5858c2ba9a)] - **zlib**: validate pledgedSrcSize for sync zstd (Archkon) [#&#8203;64601](https://redirect.github.com/nodejs/node/pull/64601)

### [`v24.20.0`](https://redirect.github.com/nodejs/node/releases/tag/v24.20.0): 2026-08-26, Version 24.20.0 'Krypton' (LTS), @&#8203;aduh95

[Compare Source](https://redirect.github.com/nodejs/node/compare/v24.19.0...v24.20.0)

##### Notable Changes

- \[[`b12bcc9ae1`](https://redirect.github.com/nodejs/node/commit/b12bcc9ae1)] - **(SEMVER-MINOR)** **async\_hooks**: add `using` scopes to `AsyncLocalStorage` (Stephen Belanger) [#&#8203;61674](https://redirect.github.com/nodejs/node/pull/61674)
- \[[`e2eb88b36b`](https://redirect.github.com/nodejs/node/commit/e2eb88b36b)] - **(SEMVER-MINOR)** **buffer**: add `end` parameter (Robert Nagy) [#&#8203;62390](https://redirect.github.com/nodejs/node/pull/62390)
- \[[`1fefdda18e`](https://redirect.github.com/nodejs/node/commit/1fefdda18e)] - **crypto**: update root certificates to NSS 3.125 (Node.js GitHub Bot) [#&#8203;64746](https://redirect.github.com/nodejs/node/pull/64746)
- \[[`4a158cf1ab`](https://redirect.github.com/nodejs/node/commit/4a158cf1ab)] - **doc**: add MikeMcC399 as collaborator (Mike McCready) [#&#8203;64656](https://redirect.github.com/nodejs/node/pull/64656)
- \[[`d4cafce076`](https://redirect.github.com/nodejs/node/commit/d4cafce076)] - **(SEMVER-MINOR)** **lib,permission**: add `permission.drop` (Rafael Gonzaga) [#&#8203;62672](https://redirect.github.com/nodejs/node/pull/62672)
- \[[`b3cfb55267`](https://redirect.github.com/nodejs/node/commit/b3cfb55267)] - **(SEMVER-MINOR)** **loader**: implement package maps (Maël Nison) [#&#8203;62239](https://redirect.github.com/nodejs/node/pull/62239)
- \[[`cd1eb3e60b`](https://redirect.github.com/nodejs/node/commit/cd1eb3e60b)] - **(SEMVER-MINOR)** **src,permission**: add `--permission-audit` (RafaelGSS) [#&#8203;61869](https://redirect.github.com/nodejs/node/pull/61869)
- \[[`28dc85d8d2`](https://redirect.github.com/nodejs/node/commit/28dc85d8d2)] - **(SEMVER-MINOR)** **stream**: add `node:stream/iter` implementation (James M Snell) [#&#8203;62066](https://redirect.github.com/nodejs/node/pull/62066)
- \[[`d31c168740`](https://redirect.github.com/nodejs/node/commit/d31c168740)] - **(SEMVER-MINOR)** **test\_runner**: add `context.log()` and test:log event (Moshe Atlow) [#&#8203;64389](https://redirect.github.com/nodejs/node/pull/64389)
- \[[`add1edbc42`](https://redirect.github.com/nodejs/node/commit/add1edbc42)] - **(SEMVER-MINOR)** **test\_runner**: report `entryFile` in `TestStream` events (Moshe Atlow) [#&#8203;64309](https://redirect.github.com/nodejs/node/pull/64309)
- \[[`d937c8c6cd`](https://redirect.github.com/nodejs/node/commit/d937c8c6cd)] - **(SEMVER-MINOR)** **wasm**: enable JSPI (Guy Bedford) [#&#8203;59941](https://redirect.github.com/nodejs/node/pull/59941)

##### Commits

- \[[`1822c0f335`](https://redirect.github.com/nodejs/node/commit/1822c0f335)] - **assert,util**: fix TypeError on Maps with null keys (Paul Bouchon) [#&#8203;64441](https://redirect.github.com/nodejs/node/pull/64441)
- \[[`b12bcc9ae1`](https://redirect.github.com/nodejs/node/commit/b12bcc9ae1)] - **(SEMVER-MINOR)** **async\_hooks**: add us

> ❗ **Important**
> 
> ✂ PR body was truncated to here.


</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43OS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-14 08:03:32 -07:00
renovate[bot] 953b6a2d32 chore(deps): update lint (#697)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [oxfmt](https://oxc.rs/docs/guide/usage/formatter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt)) | [`0.66.0` → `0.67.0`](https://renovatebot.com/diffs/npm/oxfmt/0.66.0/0.67.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxfmt/0.67.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxfmt/0.66.0/0.67.0?slim=true) |
| [oxlint](https://oxc.rs/docs/guide/usage/linter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint)) | [`1.81.0` → `1.82.0`](https://renovatebot.com/diffs/npm/oxlint/1.81.0/1.82.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.82.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.81.0/1.82.0?slim=true) |

---

### Release Notes

<details>
<summary>oxc-project/oxc (oxfmt)</summary>

### [`v0.67.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxfmt/CHANGELOG.md#0670---2026-09-07)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.66.0...oxfmt_v0.67.0)

##### 🛡️ Security

- [`9a612d5`](https://redirect.github.com/oxc-project/oxc/commit/9a612d5) deps: Update npm packages ([#&#8203;26178](https://redirect.github.com/oxc-project/oxc/issues/26178)) (renovate\[bot])

</details>

<details>
<summary>oxc-project/oxc (oxlint)</summary>

### [`v1.82.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1820---2026-09-07)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.81.0...oxlint_v1.82.0)

##### 🚀 Features

- [`6a0e19c`](https://redirect.github.com/oxc-project/oxc/commit/6a0e19c) linter/eslint/no-unmodified-loop-condition: Support `checkConditionalExpressions` option ([#&#8203;26249](https://redirect.github.com/oxc-project/oxc/issues/26249)) (camc314)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43OS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-14 08:03:13 -07:00
renovate[bot] 07b8abbb73 chore(deps): update dependency jdx/mise to v2026.9.7 (#696)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jdx/mise](https://redirect.github.com/jdx/mise) | patch | `v2026.9.2` → `v2026.9.7` |

---

### Release Notes

<details>
<summary>jdx/mise (jdx/mise)</summary>

### [`v2026.9.7`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.7): : Lockfile revision 2 with npm and Python dependency graphs, dotfile conflict inspection

[Compare Source](https://redirect.github.com/jdx/mise/compare/v2026.9.6...v2026.9.7)

This release introduces `mise.lock` revision 2, which records complete transitive dependency graphs for npm tools (via embedded aube) and Python tools (via uv) in native sidecar files, and adds `mise bootstrap dotfiles conflicts` for inspecting dotfile sync conflicts before resolving them. It also lets dotfile templates consume bootstrap secrets, stops `minimum_release_age` from rejecting versions already committed to a lockfile, and closes a security gap in `history.describe_command`.

##### Added

- **lock:** Lockfile revision 2 records the full dependency graph of npm tools installed by embedded aube and of `pypi:` tools installed by uv, then replays it with a strict frozen install so two projects on the same top-level version can still receive their own reviewed transitive graph. Graphs live in native sidecar files (`uv.lock` / `aube-lock.yaml` plus a manifest) under `.mise/locks/<backend-tool>/<version>/`, referenced from `mise.lock` by relative path and SHA-256 digest, so the lockfile itself stays small. Commit the sidecar directory with `mise.lock`. New lockfiles use revision 2; existing revision 0 and 1 files keep their format until you run `mise lock --upgrade`. `mise lock --bump <tool>` refreshes a tool's transitive graph even when its top-level version is unchanged, ordinary `mise install` validates and accepts hand-edited sidecars, and `mise install --locked` rejects digest mismatches until you run `mise lock`. Python graph locking requires uv 0.12.10 or newer and published wheels for the target platform; Git sources, standalone pipx installs, and free-form `uvx_args`/`pipx_args` stay version-only. ([#&#8203;13131](https://redirect.github.com/jdx/mise/pull/13131), [#&#8203;13146](https://redirect.github.com/jdx/mise/pull/13146) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise lock --upgrade        # move an existing lockfile to revision 2 and resolve graphs
  mise install --locked      # replay the recorded graphs
  mise lock --bump pypi:black  # refresh Black's dependencies without changing its version
  ```
- **pypi:** `pypi:` is now the preferred name for the Python CLI backend; `pipx:` remains fully supported as an alias with no warnings, and settings accept both `pypi.*` and `pipx.*` names. The two spellings are distinct tool identities (`pypi-black` vs `pipx-black` install directories and lock entries), so switching spelling creates a new installation. ([#&#8203;13146](https://redirect.github.com/jdx/mise/pull/13146) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** `mise bootstrap dotfiles conflicts [PATH...]` shows a read-only comparison of the saved local and fetched remote versions of a conflicted dotfile so you can decide between `--take-remote` and `--keep-local` with full context. The default output is a unified diff including file-mode changes; `--difftool` opens the configured Git `diff.tool` (falling back to `merge.tool`) and `--tool <name>` picks one explicitly. Encrypted contents are decrypted only into private temporary files, and inspection never modifies either side or marks the conflict resolved. Bootstrap secrets are also now resolved from the same composed config maps as dotfile discovery, so root-scoped dotfile templates can use secrets declared by their bootstrap root. ([#&#8203;13144](https://redirect.github.com/jdx/mise/pull/13144) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise bootstrap dotfiles conflicts ~/.config/mise/config.toml
  mise bootstrap dotfiles conflicts --difftool ~/.config/mise/config.toml
  ```
- **dotfiles:** Dotfile templates (`mode = "template"`) can reference `[bootstrap.secrets]` values with `{{ secret(name="...") }}`, matching managed bootstrap file templates. Dotfiles commands that render templates (`add`, `apply`, `diff`, `edit`, `status`, `unapply`) accept `--prompt-secrets`; without an available value, rendering fails closed. A full `mise bootstrap` run preflights dotfile templates before making changes, `mise bootstrap status` reports secrets used only by dotfiles, and textual diffs redact resolved secret values. ([#&#8203;13140](https://redirect.github.com/jdx/mise/pull/13140) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.secrets]
  api_token = "EXAMPLE_API_TOKEN"

  [dotfiles."~/.config/example/credentials"]
  source = "dotfiles/credentials.tmpl"
  mode = "template"
  ```

##### Fixed

- **lock:** Installing from a committed `mise.lock` no longer fails when the locked release is younger than `minimum_release_age`. The cutoff still applies when resolving unlocked fuzzy requests and when generating or bumping a lockfile, and `npm:`/`pypi:` still forward it to unpinned transitive dependencies, but a reviewed lock entry now reproduces immediately in CI instead of waiting for the release to cool. ([#&#8203;13128](https://redirect.github.com/jdx/mise/pull/13128) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** A `.python-version` (or other idiomatic version file) containing `system` selects the system interpreter without printing the mise-specific `@system` deprecation warning, matching the existing `.tool-versions` exception. Explicit `python@system` requests from mise configuration or command arguments still warn. ([#&#8203;13132](https://redirect.github.com/jdx/mise/pull/13132) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** Embedded aube is updated to 2.2.16, fixing the Bun checksum install regression and ensuring local npm tarballs keep their manifest package name. ([#&#8203;13145](https://redirect.github.com/jdx/mise/pull/13145) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **registry:** The `mc` shorthand uses `aqua:minio/mc` again now that the upstream Aqua registry entry is restored, with `asdf:mise-plugins/mise-mc` kept as the fallback. ([#&#8203;13124](https://redirect.github.com/jdx/mise/pull/13124) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Security

- **history:** `history.describe_command` is now global-only. Previously an implicitly trusted project could set it and have a later dotfiles history checkpoint execute the project-controlled command with unencrypted tracked-file diffs. The setting is honored only from system/global configuration or `MISE_HISTORY_DESCRIBE_COMMAND`; project values are ignored with a warning. ([#&#8203;13134](https://redirect.github.com/jdx/mise/pull/13134) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **oci:** `mise oci build` now renders dotfile templates with a restricted engine: `secret()` is rejected and the `env` context, `get_env()`, `exec()`, and `read_file()` are unavailable, so ambient credentials cannot be baked into a publishable image layer. ([#&#8203;13140](https://redirect.github.com/jdx/mise/pull/13140) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Breaking Changes

- **Lockfile revision 2 is not readable by older mise versions.** Newly created lockfiles use revision 2, and existing files switch only when you run `mise lock --upgrade`. Upgrade collaborators and CI to this release before committing a revision 2 `mise.lock`, and commit the `.mise/locks/` (or `.config/mise/locks/`) sidecar directory alongside it. Revision 2 `--locked` installs fail if a recorded graph is missing or its digest does not match. If you gitignore `mise.local.lock`, also ignore its matching sidecar subdirectory (for example `.mise/locks/mise.local/`).
- **`history.describe_command` in project configuration is ignored.** Move it to `~/.config/mise/config.toml` or set `MISE_HISTORY_DESCRIBE_COMMAND`.
- **`mise oci build` dotfile templates** can no longer call `secret()`, `get_env()`, `exec()`, or `read_file()` or read the `env` context.

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.6...v2026.9.7>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.6`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.6): : Project daemons, mise doctor project, and vfox backend tool discovery

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.5...v2026.9.6)

This release adds experimental project daemons backed by pitchfork, a `mise doctor project` command for project-declared diagnostic checks, and tool discovery from vfox backend plugins in `mise search`. It also changes the HTTP backend's default install layout so uninstall and prune reclaim disk space, speeds up warm `lockfile_mode = "generate"` installs and repeated OCI builds, and fixes a batch of nushell, monorepo, lockfile, brew, and Windows bootstrap issues.

#### Highlights

- **Services next to tools:** `[daemons]` declares background processes and PostgreSQL/Redis presets in `mise.toml`, managed through `mise daemons` and optionally started when you enter the project. `[doctor.checks.<name>]` lets projects declare their own environment probes for `mise doctor project`.
- **Discovery and output:** `mise search`, shell completion, and interactive `mise use` now include tools published by installed vfox backend plugins, and `settings.truncate` / `--no-truncate` disable terminal-width truncation (automatically when a coding agent is detected).
- **Storage and speed:** HTTP tools now extract into their own install directory by default (opt back into deduplication with `shared_extraction = true`), warm generate-mode installs skip needless lockfile rewrites, and OCI builds share a local tool-layer cache.

#### Added

- **daemons:** New experimental `[daemons]` section and `mise daemons` command family (`start`, `stop`, `restart`, `ls`, `status`, `logs`, `tui`) manage project background processes with pitchfork. PostgreSQL and Redis presets install the database as a tool (participating in lockfiles), supply connection environment variables and readiness checks, and keep project data across stop/start. Daemons with `auto = ["start", "stop"]` start when entering the project from an activated Bash, Zsh, or Fish shell and are released when the last shell session leaves. Requires `experimental = true` and pitchfork 2.25.0 or later; database presets are Unix-only and PostgreSQL uses loopback trust authentication intended for local development. ([#&#8203;13085](https://redirect.github.com/jdx/mise/pull/13085) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [settings]
  experimental = true

  [daemons]
  postgres = "18"
  redis = "8"

  [daemons.web]
  run = "npm run dev"
  port = 3000
  auto = ["start", "stop"]
  ```
- **doctor:** `mise doctor project` runs checks declared in `[doctor.checks.<name>]` with the project's environment and installed tools, reporting PASS/FAIL/error/skipped per check in text or `--json`. Checks support `description`, `hint`, `timeout` (default `10s`), `dir`, `shell`, and `os` selectors, run concurrently under the `jobs` limit, and exit nonzero when any check fails. Ordinary `mise doctor` does not run them, and hints are never executed. A follow-up aligned `dir` resolution with task conventions (config root for project configs including `~/mise.toml`, `~/` expansion), fixed head-of-line blocking when one probe hangs, and kept `nohup mise doctor project` alive on SIGHUP. ([#&#8203;13062](https://redirect.github.com/jdx/mise/pull/13062), [#&#8203;13089](https://redirect.github.com/jdx/mise/pull/13089) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [doctor.checks.openssl]
  description = "OpenSSL development files are discoverable"
  run = "pkg-config --exists openssl"
  hint = "Run `mise bootstrap packages apply` to install the declared build dependencies."
  timeout = "5s"
  os = ["linux", "macos"]
  ```
- **vfox:** Tools provided by installed vfox backend plugins now appear in `mise search`, shell completion, and interactive `mise use`, namespaced as `<plugin>:<tool>`. Plugins can implement `BackendListTools` for a finite catalog and/or `BackendSearchTools` for query-driven discovery in large ecosystems; a prefixed query like `npm:eslint` is routed only to that plugin. Results are cached, slow plugins fall back to stale cache, and existing plugins need no changes. `mise registry` remains registry-only. ([#&#8203;13111](https://redirect.github.com/jdx/mise/pull/13111) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **cli:** New `settings.truncate` (and `MISE_TRUNCATE`, default `true`) controls terminal-width shortening of table cells and task metadata. `mise ls`, `mise config ls`, and `mise bootstrap dotfiles status` gain `--truncate` / `--no-truncate`, and output is kept complete automatically when a known coding agent is detected. ([#&#8203;13112](https://redirect.github.com/jdx/mise/pull/13112) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise bootstrap dotfiles status --no-truncate
  ```
- **bootstrap:** `[bootstrap.macos.dock]` gains `apps`, an ordered list of pinned application paths. Status compares identity and order (ignoring Dock-added metadata), apply adds, removes, and reorders application tiles while preserving other tiles and `persistent-others`, and an empty list removes all application tiles. Paths must be absolute or home-relative `.app` bundles. ([#&#8203;13075](https://redirect.github.com/jdx/mise/pull/13075) by [@&#8203;azohra](https://redirect.github.com/azohra))

  ```toml
  [bootstrap.macos.dock]
  apps = [
    "/System/Applications/Utilities/Terminal.app",
    "/Applications/Firefox.app",
  ]
  ```
- **bootstrap:** `mise bootstrap packages where brew:<formula>` prints an installed formula's stable `opt` root (for example `/opt/homebrew/opt/unzip`), so scripts can put keg-only executables on PATH without hardcoding the Homebrew prefix or Cellar version. Missing installs exit nonzero with empty stdout. ([#&#8203;13083](https://redirect.github.com/jdx/mise/pull/13083) by [@&#8203;himkt](https://redirect.github.com/himkt))
- **dotfiles:** Destination `variants` can omit `source` when every variant sets a `target`; the entry key is then resolved as a relative path under `settings.dotfiles.root` instead of next to `mise.toml`. Parent traversal is rejected. ([#&#8203;13087](https://redirect.github.com/jdx/mise/pull/13087) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [dotfiles."vscode/settings.json"]
  mode = "copy"
  variants = [
    { os = "macos", target = "~/Library/Application Support/Code/User/settings.json" },
    { os = "linux", target = "~/.config/Code/User/settings.json" },
  ]
  ```
- **fmt:** `mise fmt` now sorts lists whose order has no meaning: `redactions` lexically, and task `sources`/`outputs`, `task_templates` sources/outputs, `task_config.global_inputs`, and `input_groups` by reach (`@group:` references, then globs, then literal paths). Lists containing `!` exclusions, entries starting with template syntax, or comments are left untouched, and precedence-sensitive lists such as `env_file`, `tools.*`, `includes`, and `depends` are never sorted. ([#&#8203;13058](https://redirect.github.com/jdx/mise/pull/13058) by [@&#8203;jrandolf](https://redirect.github.com/jrandolf))
- **oci:** `mise oci build` gains `--no-cache` to bypass the new local tool-layer cache; entries live under each tool's cache directory and are removed by `mise cache clear TOOL`. ([#&#8203;13056](https://redirect.github.com/jdx/mise/pull/13056) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Changed

- **http:** New `http:` installations extract directly into their own install directory, so `mise uninstall` and `mise prune` now remove their files instead of leaving payloads in `$MISE_DATA_DIR/http-tarballs/`. Set `shared_extraction = true` on a tool to keep the previous deduplicated symlink layout. Existing symlinked installs keep working; `mise install --force <tool>` migrates one to independent files without disturbing other installs that share the content. Legacy `http-tarballs` entries are not reclaimed automatically. Shared raw and compressed binary caches now also include the executable filename in their key, so differently named tools no longer reuse the wrong filename. ([#&#8203;13059](https://redirect.github.com/jdx/mise/pull/13059) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **oci:** `mise oci push --no-cache` now bypasses both the remote registry cache and the local tool-layer cache. ([#&#8203;13056](https://redirect.github.com/jdx/mise/pull/13056) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **registry:** `postgres`, `redis`, and `mongodb` now prefer `conda:` backends, installing prebuilt conda-forge binaries in seconds instead of compiling through vfox; vfox and asdf remain as fallbacks. `conda:redis-server` covers Linux and macOS only. ([#&#8203;13061](https://redirect.github.com/jdx/mise/pull/13061) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Performance

- **lockfile:** Warm `mise install` runs in `lockfile_mode = "generate"` skip scheduling work for tools whose artifact metadata is already reusable, and skip rebuilding, serializing, and staging the lockfile entirely when nothing was installed and the on-disk lock already matches (preserving comments in the file). Explicit `mise lock`, forced provenance verification, upgrades, and new platforms still regenerate. ([#&#8203;13101](https://redirect.github.com/jdx/mise/pull/13101), [#&#8203;13103](https://redirect.github.com/jdx/mise/pull/13103) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **oci:** `mise oci push --from BASE` no longer downloads base layers when the base and target live in the same repository, and `oci build`, `oci run`, and `oci push` share a local cache of packaged tool layers keyed on file contents, so repeated builds with overlapping tools skip tar and gzip work. ([#&#8203;13055](https://redirect.github.com/jdx/mise/pull/13055), [#&#8203;13056](https://redirect.github.com/jdx/mise/pull/13056) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **nushell:** `mise activate nu` no longer throws `env_variable_not_found` on every prompt or `cd` when a variable to hide is absent from the current scope; `hide-env` is now wrapped in `try`, matching the no-op behavior of other shells. ([#&#8203;13071](https://redirect.github.com/jdx/mise/pull/13071) by [@&#8203;i-api](https://redirect.github.com/i-api))
- **task:** Task-level tools are now auto-installed for tasks referenced from `run` entries, including names rendered at runtime, right before they execute; `--skip-tools` is honored and install failures are reported as task failures without blocking siblings. ([#&#8203;13086](https://redirect.github.com/jdx/mise/pull/13086) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** `[monorepo]` settings are now merged across same-directory config layers (base plus `mise.<env>.toml` overlays): omitted fields are inherited, an overlay's `config_roots` replaces the base list, and `monorepo_root = false` in an overlay disables the root and its descendant trust. ([#&#8203;13084](https://redirect.github.com/jdx/mise/pull/13084) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **lockfile:** Runtime tool requests such as `mise which hk --tool hk@latest` now use the lockfile belonging to the config that effectively defines the tool, instead of merging project and global pins and reporting a false "multiple resolutions" ambiguity or selecting an overridden pin. `mise which --tool` warns when a lower-precedence config has a matching pin the effective config lacks. ([#&#8203;13042](https://redirect.github.com/jdx/mise/pull/13042) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh))
- **lockfile:** Complete lockfile generation for Packslip tools skips platforms with no published artifact while still writing supported targets, and a verified Packslip signer may now replace legacy `github-attestations` metadata on upgrade instead of being rejected as a provenance downgrade. ([#&#8203;13102](https://redirect.github.com/jdx/mise/pull/13102), [#&#8203;13105](https://redirect.github.com/jdx/mise/pull/13105) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **upgrade:** `mise upgrade` now detects updates between letter-suffixed versions such as tmux `3.7b` to `3.7c`; `sub-N` aliases keep resolving numeric components as before. ([#&#8203;13119](https://redirect.github.com/jdx/mise/pull/13119) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew:** Formulae that are keg-only solely because macOS ships them (such as `brew:zip` and `brew:unzip`) are now linked into `<prefix>/bin` on Linux, matching Homebrew. Kegs installed by earlier mise versions stay unlinked until the next `mise bootstrap packages upgrade` or a reinstall. ([#&#8203;13108](https://redirect.github.com/jdx/mise/pull/13108) by [@&#8203;lil-lon](https://redirect.github.com/lil-lon))
- **brew-cask:** Tap casks with `preflight_steps` or `postflight_steps` no longer fail during metadata extraction; declarative `run` steps are captured as structured steps and executed by mise, with support for `must_succeed = false`. ([#&#8203;13060](https://redirect.github.com/jdx/mise/pull/13060) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** `mise bootstrap remote` on Windows now finds `ssh.exe` and `tar.exe` on PATH instead of failing with `required command 'ssh' not found`. ([#&#8203;13117](https://redirect.github.com/jdx/mise/pull/13117) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **bootstrap:** macOS defaults status explains type mismatches, showing for example `2 (real; expected integer)` instead of two identical-looking values marked `differs`. ([#&#8203;13096](https://redirect.github.com/jdx/mise/pull/13096) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **dotfiles:** `mise bootstrap dotfiles track` honors the global `yes` setting for confirmations, and warns when tracking a symlink whose resolved source is not itself tracked, suggesting the command to enroll it. ([#&#8203;13072](https://redirect.github.com/jdx/mise/pull/13072) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh), [#&#8203;13095](https://redirect.github.com/jdx/mise/pull/13095) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **schema:** The JSON schema now models Git directory `manifest` dotfile entries, restricting explicit modes to `copy` or `symlink-each` and rejecting combinations with inline content or file-edit fields. ([#&#8203;12741](https://redirect.github.com/jdx/mise/pull/12741) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **asdf:** `mise asdf install` and `mise asdf reshim` no longer re-enter the full CLI dispatch, avoiding stack overflows on small-stack Linux environments; `asdf install` now follows the same implicit config trust as `mise install`. Bash completions are regenerated for the updated usage-rs word-break handling. ([#&#8203;13114](https://redirect.github.com/jdx/mise/pull/13114) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Registry

- Added `mpv` (`conda:mpv`, Linux and macOS) ([#&#8203;13049](https://redirect.github.com/jdx/mise/pull/13049) by [@&#8203;i-api](https://redirect.github.com/i-api)), `agent-browser` (`aqua:vercel-labs/agent-browser`) ([#&#8203;13088](https://redirect.github.com/jdx/mise/pull/13088) by [@&#8203;3w36zj6](https://redirect.github.com/3w36zj6)), and `himalaya` (`github:pimalaya/himalaya`) ([#&#8203;13091](https://redirect.github.com/jdx/mise/pull/13091) by [@&#8203;i-api](https://redirect.github.com/i-api)).
- `editorconfig-checker` installs again after 4.0.1 renamed its assets and executable; the shorthand now uses the GitHub backend. ([#&#8203;13098](https://redirect.github.com/jdx/mise/pull/13098) by [@&#8203;jdx](https://redirect.github.com/jdx))
- `mc` installs from MinIO's GitHub releases instead of the retired Aqua download URL that returned HTTP 410. ([#&#8203;13113](https://redirect.github.com/jdx/mise/pull/13113) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Documentation

- The docs landing page gains interactive diagrams for project environment switching, bootstrap machine resources, tool configuration precedence, artifact cache execution, and tracked dotfile synchronization. ([#&#8203;13065](https://redirect.github.com/jdx/mise/pull/13065), [#&#8203;13066](https://redirect.github.com/jdx/mise/pull/13066), [#&#8203;13067](https://redirect.github.com/jdx/mise/pull/13067), [#&#8203;13068](https://redirect.github.com/jdx/mise/pull/13068), [#&#8203;13069](https://redirect.github.com/jdx/mise/pull/13069) by [@&#8203;jdx](https://redirect.github.com/jdx))
- The task-running guide is reorganized around finding and running tasks, passing arguments, then controlling execution. ([#&#8203;13077](https://redirect.github.com/jdx/mise/pull/13077) by [@&#8203;azohra](https://redirect.github.com/azohra))

#### New Contributors

- [@&#8203;lil-lon](https://redirect.github.com/lil-lon) made their first contribution in [#&#8203;13108](https://redirect.github.com/jdx/mise/pull/13108)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.5...v2026.9.6>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.5`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.5): : macOS defaults, per-platform dotfiles, and complete lockfiles

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.4...vfox-v2026.9.5)

This release deepens macOS bootstrap support with current-host and nested defaults, lets dotfiles and tasks adapt to the platform and to parsed arguments, and adds an opt-in trial of complete lockfile generation. It also carries a batch of install progress, self-update, brew-cask, and sandbox fixes.

#### Added

- **bootstrap:** New `[[bootstrap.macos.defaults_entries]]` blocks let you set macOS preferences explicitly with `domain`, `key`, `value`, and an optional `host` (`any` by default, or `current`), covering preferences normally written via `defaults -currentHost` while keeping the existing `[bootstrap.macos.defaults]` shorthand. Entries also accept an optional `path` to patch a nested dictionary value without replacing its siblings, preserving property-list types and creating missing parents. ([#&#8203;12983](https://redirect.github.com/jdx/mise/pull/12983) by [@&#8203;azohra](https://redirect.github.com/azohra), [#&#8203;12984](https://redirect.github.com/jdx/mise/pull/12984) by [@&#8203;azohra](https://redirect.github.com/azohra))

  ```toml
  [[bootstrap.macos.defaults_entries]]
  domain = "com.apple.dock"
  key = "autohide"
  value = true
  host = "current"
  ```
- **bootstrap:** More friendly macOS preferences: Finder folder sorting and default cloud save location, Dock autohide delay and timing (integers or floats), and keyboard automatic capitalization and spelling correction, all using snake\_case names consistent with the existing sections. ([#&#8203;13032](https://redirect.github.com/jdx/mise/pull/13032) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** `[bootstrap.files]` and `[bootstrap.directories]` entries gain `phase = "pre-packages"` so repository definitions, apt sources, and signing keys can be applied before package installation instead of only afterward. Existing declarations default to `"post-packages"`. ([#&#8203;13052](https://redirect.github.com/jdx/mise/pull/13052) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.files."/etc/apt/sources.list.d/vendor.sources"]
  source = "./files/vendor.sources"
  phase = "pre-packages"
  ```
- **bootstrap:** Ordinary `[bootstrap.files]` templates can now reference resolved `[vars]` values, alongside the existing `config_root`, `target`, and `secret()` helpers. ([#&#8203;13033](https://redirect.github.com/jdx/mise/pull/13033) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh))
- **dotfiles:** A single dotfiles source can deploy to different destinations per operating system, architecture, or mise profile using `variants` with an optional `target`. This works for `copy`, `symlink`, `symlink-each`, and `template` modes. ([#&#8203;13050](https://redirect.github.com/jdx/mise/pull/13050) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [dotfiles.settings]
  source = "dotfiles/vscode/settings.json"
  mode = "copy"
  variants = [
    { os = "macos", target = "~/Library/Application Support/Code/User/settings.json" },
    { os = "linux", target = "~/.config/Code/User/settings.json" },
  ]
  ```
- **task:** Task `sources` and `outputs` can now use `{{usage.*}}` templates, resolved per invocation from parsed arguments and flags before freshness and artifact-cache checks run, so different argument values track freshness independently. ([#&#8203;13051](https://redirect.github.com/jdx/mise/pull/13051) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew-cask:** Casks with structured `set_permissions` preflight/postflight steps now install correctly (for example `brew-cask:blender`), running an unprivileged `chmod` over resolved staged or appdir paths instead of failing with an unsupported step-type error. ([#&#8203;13043](https://redirect.github.com/jdx/mise/pull/13043) by [@&#8203;azohra](https://redirect.github.com/azohra))
- **lock:** Opt-in trial of complete lockfile generation via `lockfile_mode = "generate"` (or `MISE_LOCKFILE_MODE=generate`). The default remains incremental `merge`. Generate mode rebuilds lockfiles from current requests while treating the previous file as an immutable baseline, reusing unchanged artifacts and publishing through staged atomic writes so failures or concurrent edits do not clobber a good lockfile. This mode records only cryptographically verified provenance per target platform; `provenance_verified` is no longer treated as a trust signal. ([#&#8203;13031](https://redirect.github.com/jdx/mise/pull/13031) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **self-update:** New `disable_update_warning` setting (`MISE_DISABLE_UPDATE_WARNING`) suppresses "newer mise available" notices in `mise version`, `mise --version`, and `mise doctor`. Explicit self-update and automatic updates are unaffected. ([#&#8203;13028](https://redirect.github.com/jdx/mise/pull/13028) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **install:** Interactive installs no longer leave a permanent line for every resolved, skipped, or already-installed tool; live progress shows what is happening while the final summary lists what changed (for example `installed 1 tool in 1.1s: [email protected]`). `mise upgrade` no longer duplicates its old to new version list. ([#&#8203;13030](https://redirect.github.com/jdx/mise/pull/13030) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** Long non-TTY installs no longer flood CI logs with a snapshot every three seconds. The heartbeat now scales to roughly 10% of elapsed time, clamped between 3 seconds and 1 minute. ([#&#8203;13036](https://redirect.github.com/jdx/mise/pull/13036) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **self-update:** On 32-bit ARM, `mise self-update` now selects the correct `linux-armv7` archive instead of requesting a missing `linux-arm` one and falling back to an ARM64 binary that failed signature verification. A missing archive now fails asset selection rather than picking the wrong architecture. ([#&#8203;13023](https://redirect.github.com/jdx/mise/pull/13023) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **self-update:** npm installs now ship the instructions file that redirects update guidance to the package manager, so they no longer advertise `mise self-update`. ([#&#8203;13028](https://redirect.github.com/jdx/mise/pull/13028) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew-cask:** `mise bootstrap packages upgrade` no longer replaces the bundle of a running self-updating app (for example Chrome), which could strand helper processes and blank out tabs. Such apps are skipped while running and left to update themselves. ([#&#8203;13041](https://redirect.github.com/jdx/mise/pull/13041) by [@&#8203;azohra](https://redirect.github.com/azohra))
- **brew-cask:** The cask metadata fetch error no longer includes stale advice about installing with `brew` and a broken documentation anchor; it now gives a short, accurate message. ([#&#8203;12800](https://redirect.github.com/jdx/mise/pull/12800) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **bootstrap:** `mise bootstrap dotfiles origin set <url>` now uses the repository's own default branch when `--branch` is omitted, so repositories on `master` connect correctly instead of publishing a second root branch. A missing requested branch is now reported clearly rather than mistaken for an empty repository. ([#&#8203;13037](https://redirect.github.com/jdx/mise/pull/13037) by [@&#8203;Dhaulagiri](https://redirect.github.com/Dhaulagiri))
- **dotfiles:** History watcher locks now live alongside the history store in the state directory instead of being hashed into the cache directory, so a launchd watcher and an interactive shell using different cache directories coordinate correctly and `dotfiles status` no longer misreports `declared-not-running`. Existing watchers must be stopped and restarted with the updated binary. ([#&#8203;13038](https://redirect.github.com/jdx/mise/pull/13038) by [@&#8203;ascarter](https://redirect.github.com/ascarter))
- **sandbox:** The macOS Seatbelt profile now allows `file-read-metadata` on the ancestors of readable paths, fixing `Operation not permitted` failures when a portable Ruby resolves its own executable during third-party tap evaluation. Symlinked data directories and allow-listed paths are also handled. ([#&#8203;13039](https://redirect.github.com/jdx/mise/pull/13039) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))

#### Registry

- Added `clipboard` ([github:Slackadays/Clipboard](https://redirect.github.com/Slackadays/Clipboard)) by [@&#8203;i-api](https://redirect.github.com/i-api) in [#&#8203;13044](https://redirect.github.com/jdx/mise/pull/13044)

#### New Contributors

- [@&#8203;Dhaulagiri](https://redirect.github.com/Dhaulagiri) made their first contribution in [#&#8203;13037](https://redirect.github.com/jdx/mise/pull/13037)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.4...v2026.9.5>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.4`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.4): : Nix Bootstrap, Environment Selectors, and Man Pages

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.3...vfox-v2026.9.4)

This release expands the bootstrap package model with Nix support and environment selectors, teaches packslip tools to ship man pages, and adds a task-scoped quiet setting. It also carries a wide batch of packslip, bootstrap, and cross-platform fixes plus a major speedup to dotfiles history rebuilds.

#### Added

- **bootstrap:** Nix is now a built-in `[bootstrap.packages]` manager on Linux and macOS. Declare packages with the `nix:` prefix and apply them through your normal Nix profile; the manager supports use, apply, status JSON, and targeted upgrades while leaving Nix sources, caches, trust, and profile rollback under Nix control. This also adds `mise bootstrap packages export --format nix` to emit a deterministic NixOS module from your `nix:` declarations and `packages use --no-install` to record declarations without touching package managers. ([#&#8203;13013](https://redirect.github.com/jdx/mise/pull/13013) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.packages]
  "nix:ripgrep" = "latest"
  ```
- **bootstrap:** `[bootstrap.packages]` entries gain an `env` selector (a single environment or a list), so a package is only enabled when one of its listed mise environments is active via `-E` or `MISE_ENV`. When both `os` and `env` are set, both must match. Packages for inactive environments stay declared and are protected from pruning. ([#&#8203;12956](https://redirect.github.com/jdx/mise/pull/12956) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.packages]
  "brew:postgresql" = { version = "latest", env = ["dev", "test"] }
  ```
- **packslip:** Packslip-installed tools can now ship man pages declared as static `man` resources, alongside completions and agent skills. While a tool version is active, mise prepends its man root to `MANPATH` and keeps system and caller-defined paths visible, so `man <tool>` works. Existing installs need to be reinstalled to pick up man pages. ([#&#8203;13012](https://redirect.github.com/jdx/mise/pull/13012) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **task:** New `task.quiet` setting (and `MISE_TASK_QUIET`) suppresses mise's own task messages, prefixes, and command-echo headers without hiding task output or affecting other commands. The bundled `output = "quiet"` mode is deprecated in favor of explicit output style plus this setting; removal is scheduled for 2027.9.3. ([#&#8203;12980](https://redirect.github.com/jdx/mise/pull/12980) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **install:** A lazy tool whose `depends` target is also lazy now installs correctly on first use through a shim, `mise x`, or a task; mise installs the provider together with its still-missing configured dependencies instead of failing the preflight. ([#&#8203;12997](https://redirect.github.com/jdx/mise/pull/12997) by [@&#8203;balintant](https://redirect.github.com/balintant))
- **backend:** 32-bit ARM resolution now uses Go's canonical `arm` architecture name for Aqua (so registry replacements apply), and automatic GitHub release asset selection no longer picks a generic `source.tar.gz` when no published binary targets the host. ([#&#8203;13004](https://redirect.github.com/jdx/mise/pull/13004) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Homebrew bottles with hard-linked Mach-O executables are now signed correctly on macOS. Every hard-link alias is tracked and included in the signing list after relocation, fixing cases like fish where an aliased binary was killed with SIGKILL after a successful install. ([#&#8203;12988](https://redirect.github.com/jdx/mise/pull/12988) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh))
- **packslip:** `mise lock --platform` now verifies the signed release manifest and records the correct URL, checksum, size, and signer for each requested target platform (including Windows x64), so locked installs work across platforms. ([#&#8203;13002](https://redirect.github.com/jdx/mise/pull/13002) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** On glibc Linux, an artifact's `glibc_min` is now honored during selection: when the GNU build requires a newer glibc than the host, mise falls back to a matching static musl build if one exists. ([#&#8203;13009](https://redirect.github.com/jdx/mise/pull/13009) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** Windows installs now link shims with the correct `.exe` filename, while Unix keeps extensionless names. ([#&#8203;13006](https://redirect.github.com/jdx/mise/pull/13006) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** Activated man roots are now scoped to Packslip-backed tool versions, and the caller's original `MANPATH` is included in the environment-cache identity so one cached process cannot serve another caller's `MANPATH`. ([#&#8203;13016](https://redirect.github.com/jdx/mise/pull/13016) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **prune:** `mise prune` no longer exits successfully without pruning when it cannot show a confirmation prompt; it now fails with guidance to pass `--yes` or set `MISE_YES=1`. Follow-up prunes from commands like `mise unuse` still leave installs in place when nobody can answer. ([#&#8203;13003](https://redirect.github.com/jdx/mise/pull/13003) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **generate:** `mise generate tool-stub` now respects a stub's top-level `os` selector when deciding whether to write a Windows `.cmd` launcher, so a linux/macos-only stub no longer produces a launcher even with `--lock`. ([#&#8203;13008](https://redirect.github.com/jdx/mise/pull/13008) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **dotfiles:** History path filters resolve correctly when `HOME` is a symlink, so aliased, canonical, and tilde paths address the same history entry and `show latest`/`diff` no longer report a missing checkpoint. ([#&#8203;12982](https://redirect.github.com/jdx/mise/pull/12982) by [@&#8203;azohra](https://redirect.github.com/azohra))
- **brew:** The published crate now includes the tap formula and cask metadata shims, fixing `cargo install --locked mise` builds from crates.io. ([#&#8203;13001](https://redirect.github.com/jdx/mise/pull/13001) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Changed

- **bootstrap:** `[bootstrap].config_roots` and the `mise bootstrap config-roots` command are now deprecated (hidden from help and removed from public docs), with removal scheduled for mise 2027.3.3. Existing configurations keep working during the compatibility window but emit a warning directing users to global or system bootstrap configuration. ([#&#8203;13010](https://redirect.github.com/jdx/mise/pull/13010) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **registry:** Added basecamp to the registry. ([#&#8203;13024](https://redirect.github.com/jdx/mise/pull/13024))

#### Performance

- **history:** Dotfiles history index rebuilds are dramatically faster, reconstructing checkpoint metadata in-process with gix instead of spawning Git per checkpoint. On an 80-checkpoint, 44-file reproducer this cut a rebuild from roughly 26-28 seconds and thousands of Git processes down to under a second. Removed annotations are now correctly dropped after a rebuild, and SHA-256 history repositories are supported. ([#&#8203;13011](https://redirect.github.com/jdx/mise/pull/13011) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Deprecated

- The task `output = "quiet"` mode (use `task.quiet` instead; removal in 2027.9.3) ([#&#8203;12980](https://redirect.github.com/jdx/mise/pull/12980)).
- `[bootstrap].config_roots` and `mise bootstrap config-roots` (removal in mise 2027.3.3) ([#&#8203;13010](https://redirect.github.com/jdx/mise/pull/13010)).

#### New Contributors

- [@&#8203;balintant](https://redirect.github.com/balintant) made their first contribution in [#&#8203;12997](https://redirect.github.com/jdx/mise/pull/12997)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.3...v2026.9.4>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.3`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.3): : Winget bootstrap, signed vfox plugins, and faster inline commands

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.2...vfox-v2026.9.3)

This release brings Windows bootstrap into the package-manager era with WinGet support, extends the packslip signing model to vfox plugins, and speeds up simple tasks by skipping the shell on Unix. It also carries a batch of bootstrap, sandbox, and Windows ARM64 fixes.

##### Added

- **bootstrap:** WinGet is now a built-in `[bootstrap.packages]` manager on Windows, so `mise bootstrap packages` can check status, install, and upgrade apps by exact package ID. It supports version pins, source refresh, and automatic source-agreement acceptance. Scoop, Chocolatey, and package removal remain future work. ([#&#8203;12928](https://redirect.github.com/jdx/mise/pull/12928) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [bootstrap.packages]
  "winget:Microsoft.PowerShell" = "latest"
  ```
- **vfox:** External vfox plugins can now be installed from signed packslip archives instead of a Git clone, reusing the packslip backend's release selection, signature verification, digest checks, and signer pins. This is aimed at non-registry plugins; registry defaults and embedded plugins are unchanged. ([#&#8203;12948](https://redirect.github.com/jdx/mise/pull/12948) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise plugins install vfox:bfs 'packslip:mise-plugins/vfox-bfs#0.1.0'
  ```
- **bootstrap:** Dotfile line edits gain `position = "prepend"` to insert a managed line at the top of a file instead of appending (the default). An existing exact match is left in place, and unrelated bytes, CRLF endings, and a UTF-8 BOM are preserved. ([#&#8203;12941](https://redirect.github.com/jdx/mise/pull/12941) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [dotfiles]
  "/etc/zshrc/zdotdir" = { line = 'ZDOTDIR=$HOME/.config/zsh/', position = "prepend" }
  ```
- **bootstrap:** macOS bootstrap `defaults` now support nested TOML arrays and tables (for example Dock `persistent-apps`), reading and writing preferences through Core Foundation so booleans, numbers, strings, arrays, and dictionaries keep their plist types. Collections apply as whole-value replacements; datetimes and binary plist data are still skipped with a warning. ([#&#8203;12947](https://redirect.github.com/jdx/mise/pull/12947) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Changed

- **bootstrap:** Adopting global configuration or a shared dotfile-history setup repository is now spelled `mise bootstrap --adopt` (and `mise bootstrap remote --adopt`), separate from `mise bootstrap --from` for running a bootstrap project. The dotfiles, history, setup, and services guides were reorganized to lead with usable examples. The previous `--from-git` spelling still works as a hidden alias but warns and is scheduled for removal in mise 2026.10.0. ([#&#8203;12953](https://redirect.github.com/jdx/mise/pull/12953) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Fixed

- **github:** Git operations (clones, fetches, submodules) that GitHub now challenges with HTTP 401 on public repositories can authenticate using mise's existing GitHub token sources via a command-scoped `github.com` HTTPS credential helper. Tokens are kept out of command arguments and URLs, and Enterprise hosts are out of scope. ([#&#8203;12945](https://redirect.github.com/jdx/mise/pull/12945) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** The default inline task shell is now `sh -o errexit -c` so shells that consume the argument right after `-c` (such as FreeBSD sh) run your command instead of trying to execute `-o`. ([#&#8203;12949](https://redirect.github.com/jdx/mise/pull/12949) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **windows:** Fixed two Windows ARM64 issues: stale command-wrapper shims (like a leftover `cargo.exe`) that caused recursive `.exe`/`.cmd` dispatch after shim-mode changes are now pruned, and Aqua ARM64 emulation candidates apply the registry's `amd64` replacement so tools like LuaLS resolve their published `win32-x64` assets. ([#&#8203;12931](https://redirect.github.com/jdx/mise/pull/12931) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Bootstrap no longer aborts on Arch when `pacman -Q` prints a file advisory for a missing package that shares its name with a directory in the current working directory (for example a `fish/` folder in a dotfiles checkout). ([#&#8203;12932](https://redirect.github.com/jdx/mise/pull/12932) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh))
- **sandbox:** Read-restricted macOS Seatbelt profiles now allow metadata-only access to `/private`, fixing startup failures for load-relative binaries like Ruby 4.0.6 when run under `/private/tmp`. Directory listings and descendant reads stay denied. ([#&#8203;12940](https://redirect.github.com/jdx/mise/pull/12940) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** The macOS notification helper is now built and Developer ID-signed at release time and spawned with a start handshake, so short `dotfiles sync` runs no longer race the helper and macOS accepts it. Unsigned source builds (such as Homebrew) disable notifications and warn during origin setup. ([#&#8203;12946](https://redirect.github.com/jdx/mise/pull/12946) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **vfox:** vfox install phases now flow through the active install progress reporter, mapping noisy download, checksum, attestation, and extraction lines into concise phases instead of scrolling log output. ([#&#8203;12944](https://redirect.github.com/jdx/mise/pull/12944) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **java:** The `java -version` banner shown during install verification now stays inside the interactive install progress row instead of printing to the terminal. ([#&#8203;12943](https://redirect.github.com/jdx/mise/pull/12943) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **version:** The auto-update hint from `mise version` now uses the `mise settings auto_update=true` syntax to match the rest of `mise settings`. ([#&#8203;12957](https://redirect.github.com/jdx/mise/pull/12957) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Performance

- On Unix, plain inline commands (like `node build.js`) now run directly when a conservative planner deems them safe, skipping the shell wrapper. Anything involving shell syntax, quoting, expansion, builtins, ambiguous PATH lookup, explicit shell settings, or sandboxed/audited tasks still uses the shell, and Windows is unchanged. In a microbenchmark of short commands this cut about 11% off elapsed time. ([#&#8203;12950](https://redirect.github.com/jdx/mise/pull/12950) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Registry

- Added cargo-deny, kingfisher, nushell, shellharden, sherif, and tauri-cli. ([#&#8203;12938](https://redirect.github.com/jdx/mise/pull/12938) by [@&#8203;jrandolf](https://redirect.github.com/jrandolf))

##### Documentation

- Restored the prominent project name and pronunciation on the homepage. ([#&#8203;12936](https://redirect.github.com/jdx/mise/pull/12936) by [@&#8203;jdx](https://redirect.github.com/jdx))
- Dropped the "you need Git installed" prerequisite from the install docs. ([#&#8203;12958](https://redirect.github.com/jdx/mise/pull/12958) by [@&#8203;jdx](https://redirect.github.com/jdx))

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.2...v2026.9.3>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43OS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-14 08:03:05 -07:00
renovate[bot] 9c43369ad3 chore(deps): update dependency @types/node to v24.13.4 (#695)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`24.13.3` → `24.13.4`](https://renovatebot.com/diffs/npm/@types%2fnode/24.13.3/24.13.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/24.13.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/24.13.3/24.13.4?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43OS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-14 08:02:50 -07:00
renovate[bot] ca45510e08 chore(deps): update dependency @expo/batcher to v1.1.2 (#691)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| @&#8203;expo/batcher | [`1.0.0` → `1.1.2`](https://renovatebot.com/diffs/npm/@expo%2fbatcher/1.0.0/1.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@expo%2fbatcher/1.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@expo%2fbatcher/1.0.0/1.1.2?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42MS4zIiwidXBkYXRlZEluVmVyIjoiNDQuNjEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-08 08:18:03 -07:00
renovate[bot] 4f5d13d5c7 chore(deps): update jest to v30.5.1 (#693)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@jest/globals](https://redirect.github.com/jestjs/jest) ([source](https://redirect.github.com/jestjs/jest/tree/HEAD/packages/jest-globals)) | [`30.4.1` → `30.5.1`](https://renovatebot.com/diffs/npm/@jest%2fglobals/30.4.1/30.5.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@jest%2fglobals/30.5.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@jest%2fglobals/30.4.1/30.5.1?slim=true) |
| [jest](https://jestjs.io/) ([source](https://redirect.github.com/jestjs/jest/tree/HEAD/packages/jest)) | [`30.4.2` → `30.5.1`](https://renovatebot.com/diffs/npm/jest/30.4.2/30.5.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/jest/30.5.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jest/30.4.2/30.5.1?slim=true) |

---

### Release Notes

<details>
<summary>jestjs/jest (@&#8203;jest/globals)</summary>

### [`v30.5.1`](https://redirect.github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3051)

[Compare Source](https://redirect.github.com/jestjs/jest/compare/v30.5.0...v30.5.1)

##### Fixes

- `[jest-config]` Don't warn about global-only options in the config that supplies the global config - the root config a project resolves to, or the first entry of `--projects` when no root config is passed ([#&#8203;16411](https://redirect.github.com/jestjs/jest/pull/16411))
- `[jest-config, jest-types]` Stop accepting `reporters`, `coverageReporters`, `workerIdleMemoryLimit`, `cwd` and `runnerOptions` in a project config - they were silently ignored, and now warn like the other global-only options ([#&#8203;16411](https://redirect.github.com/jestjs/jest/pull/16411))
- `[jest-config, jest-validate]` Warn about `maxWorkers` and `coverageThreshold` in a project config instead of dropping them without a word ([#&#8203;16411](https://redirect.github.com/jestjs/jest/pull/16411))
- `[jest-resolve]` Match `moduleNameMapper` patterns against the specifier as written again (reverting [#&#8203;16390](https://redirect.github.com/jestjs/jest/pull/16390)) ([#&#8203;16417](https://redirect.github.com/jestjs/jest/pull/16417))
- `[jest-runtime]` Resolve package `imports` specifiers like `#dep` under ESM again ([#&#8203;16413](https://redirect.github.com/jestjs/jest/pull/16413))

##### Chore & Maintenance

- `[jest-util]` Name the `testEnvironmentOptions.globalsCleanup` option and link the docs from the `JEST-01` deprecation warning, and document the option's modes ([#&#8203;16404](https://redirect.github.com/jestjs/jest/pull/16404))

### [`v30.5.0`](https://redirect.github.com/jestjs/jest/blob/HEAD/CHANGELOG.md#3050)

[Compare Source](https://redirect.github.com/jestjs/jest/compare/v30.4.1...v30.5.0)

##### Features

- `[@jest/expect-utils, jest-mock]` Add `mockFn.whenCalledWith(...args)` for configuring return values per argument list, with first-class asymmetric-matcher support ([#&#8203;16053](https://redirect.github.com/jestjs/jest/pull/16053))
- `[@jest/expect-utils]` Export `AsymmetricMatcher` and `FunctionParameters` types (previously private to `expect`) ([#&#8203;16053](https://redirect.github.com/jestjs/jest/pull/16053))
- `[jest-circus, jest-core, jest-jasmine2, jest-test-result, jest-types]` `--collectTests` now expands `test.each`/`describe.each` cases and reports per-status counts (skipped/todo via the new `wouldRun` flag for selected tests) plus a summary line that match a real run, including under `--testNamePattern` and `.only`/`fdescribe` focus on both the circus and jasmine2 runners ([#&#8203;16259](https://redirect.github.com/jestjs/jest/pull/16259))
- `[jest-circus, jest-environment, jest-runtime, jest-types]` Add describe-level retries via `jest.retryTimes(..., {entireDescribe: true})` ([#&#8203;16322](https://redirect.github.com/jestjs/jest/pull/16322))
- `[jest-circus, jest-message-util, jest-reporters, jest-types]` Add `retryMessages` to `AssertionResult` and export `formatErrorStack`, so the retry log renders nested `cause` and `AggregateError` sections with code frames instead of serialized `[cause]:`/`[errors]:` markers ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-circus, jest-types]` Add `unhandledErrorsDetailed` to `Circus.RunResult`, so an unhandled rejection reports its `cause` chain and `AggregateError` entries with code frames instead of a pre-serialized stack ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-haste-map]` Replace `NodeWatcher` and `FSEventsWatcher` with `@parcel/watcher` for the non-watchman watch path ([#&#8203;16188](https://redirect.github.com/jestjs/jest/pull/16188))
- `[jest-resolve]` Bump `unrs-resolver` to 1.12.1, remove `jest-pnp-resolver` and unnecessary checks ([#&#8203;15721](https://redirect.github.com/jestjs/jest/pull/15721))
- `[jest-resolve]` Honor Node's `--preserve-symlinks` / `NODE_PRESERVE_SYMLINKS` in the default resolver by passing `symlinks: false` to `unrs-resolver` ([#&#8203;16260](https://redirect.github.com/jestjs/jest/pull/16260))
- `[jest-runtime]` Apply automocking and manual `__mocks__` files to synchronously evaluable ESM graphs on Node 24.9+ - static imports, dynamic `import()` and `require()` of an ESM file now generate an automock from the real module's namespace instead of failing with "Attempting to import a mock without a factory". Graphs that need async evaluation (top-level await) or an async-only resolver or transformer still throw ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Route `process.getBuiltinModule` through the sandbox, so it returns the sandbox `process` and the hooked `node:module` instead of the host's ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Throw an actionable error from `module.register()` and `module.registerHooks()` inside a test - the hooks attached to the loader running Jest itself, never saw the sandboxed requires they were meant for, and stayed registered for every later test file in the worker ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Surface resolution and import-attribute errors in an ESM graph before executing any of its CJS dependencies on Node 24.9+, matching Node's run-nothing-on-a-broken-graph behavior; the legacy loader on older versions keeps its linking-time execution order ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Throw `ERR_SOURCE_PHASE_NOT_DEFINED` with an actionable message for `import source` and `import.source()`, instead of failing at instantiation with V8's bare "Source phase import object is not defined" ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Emit the JSON-without-import-attribute deprecation warning once per test file instead of once per worker, so it is no longer silently swallowed for every file after the first ([#&#8203;16391](https://redirect.github.com/jestjs/jest/pull/16391))
- `[jest-runtime]` Set `import.meta.main` to `true` in the test file and `false` in every module it loads, matching Node 24+ ([#&#8203;16367](https://redirect.github.com/jestjs/jest/pull/16367))
- `[jest-runtime]` Resolve the `module-sync` export condition, so a package that exposes its ESM entry point for `require()` loads the same file Node would ([#&#8203;16336](https://redirect.github.com/jestjs/jest/pull/16336))
- `[jest-snapshot]` Add external snapshot paths to custom reporter failure details ([#&#8203;16374](https://redirect.github.com/jestjs/jest/pull/16374))

##### Fixes

- `[jest-console, jest-reporters]` `CustomConsole` now buffers console output so `TestResult.console` is populated for reporters when `verbose` is enabled, while `GitHubActionsReporter` avoids replaying buffered output in verbose mode ([#&#8203;16155](https://redirect.github.com/jestjs/jest/pull/16155))
- `[expect, jest-message-util, jest-pattern, jest-regex-util, jest-util]` Revert `node:` protocol imports to restore webpack/browser-bundle compatibility ([#&#8203;16167](https://redirect.github.com/jestjs/jest/pull/16167))
- `[expect]` Widen `toMatchObject` and `objectContaining` parameter type from `Record<string, unknown>` to `object` so class instances are accepted ([#&#8203;16196](https://redirect.github.com/jestjs/jest/pull/16196))
- `[jest-circus]` Call a generator test body with the shared test context, so `this` matches what a regular test function receives ([#&#8203;16347](https://redirect.github.com/jestjs/jest/pull/16347))
- `[jest-circus]` Capture the error listeners of the parent process instead of the in-sandbox `process`, so listeners registered before the test file survive teardown and sandbox listeners no longer leak onto the parent ([#&#8203;16347](https://redirect.github.com/jestjs/jest/pull/16347))
- `[jest-circus]` Clear `currentlyRunningTest` after skipped and todo tests ([#&#8203;16342](https://redirect.github.com/jestjs/jest/pull/16342))
- `[jest-circus]` Prevent late `done()` callbacks from affecting later test or hook invocations ([#&#8203;16343](https://redirect.github.com/jestjs/jest/pull/16343))
- `[jest-circus, jest-jasmine2]` Honor `--expand` when formatting `node:assert` failures, instead of always collapsing the diff ([#&#8203;16347](https://redirect.github.com/jestjs/jest/pull/16347))
- `[jest-circus, jest-jasmine2, jest-message-util]` Serialize the inner errors of an `AggregateError` into `failureMessages`, `retryReasons` and `unhandledErrors`, so `--json` output and reporter annotations include them ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-circus, jest-snapshot]` Keep snapshot state and counts correct when a test retries ([#&#8203;16344](https://redirect.github.com/jestjs/jest/pull/16344))
- `[@jest/create-cache-key-function]` Include the caller support flags in the generated key, so a transformer that emits ESM or CJS based on them no longer shares one cache entry between the two ([#&#8203;16331](https://redirect.github.com/jestjs/jest/pull/16331))
- `[@jest/create-cache-key-function]` Include the stringified project config in the generated key, so editing a transformer's own settings invalidates what it cached ([#&#8203;16331](https://redirect.github.com/jestjs/jest/pull/16331))
- `[@jest/transform]` Include the caller support flags in a transform's cache key, so a file transformed both as ESM and as CJS no longer serves one shape's output for the other ([#&#8203;16331](https://redirect.github.com/jestjs/jest/pull/16331))
- `[jest-config]` Add missing `findRelatedTests`, `outputFile`, and `replname` entries to `ValidConfig` so they no longer trigger spurious "Unknown option" warnings ([#&#8203;16224](https://redirect.github.com/jestjs/jest/pull/16224))
- `[jest-config]` Use `--config` for the global config when multiple `--projects` are specified ([#&#8203;16273](https://redirect.github.com/jestjs/jest/pull/16273))
- `[jest-core]` Serialize `bigint` values in `--json` and `--outputFile` output as their literal form (`4n`), instead of failing the run with `TypeError: Do not know how to serialize a BigInt` ([#&#8203;16338](https://redirect.github.com/jestjs/jest/pull/16338))
- `[jest-core]` Do not report a `CustomGC` async resource (used by N-API addons such as napi-rs for per-isolate GC bookkeeping) as an open handle, since it is `napi_unref`'d by the addon and can never keep the event loop alive ([#&#8203;16379](https://redirect.github.com/jestjs/jest/pull/16379))
- `[jest-each]` Keep a `$&`, `` $` ``, `$'` or `$$` inside a `%p` param value out of the replacement, so the title shows the value instead of the text around it ([#&#8203;16338](https://redirect.github.com/jestjs/jest/pull/16338))
- `[jest-each]` Interpolate a `bigint` into a `%j` title as its literal form (`"4n"`) at any depth, instead of throwing `TypeError: Do not know how to serialize a BigInt` while collecting the tests ([#&#8203;16338](https://redirect.github.com/jestjs/jest/pull/16338))
- `[jest-environment, jest-runtime]` Bind `sandboxInjectedGlobals` to the right values when `injectGlobals` is `false`, instead of shifting every one of them by a position ([#&#8203;16377](https://redirect.github.com/jestjs/jest/pull/16377))
- `[jest-environment-node, jest-util]` Only warn about a conflicting `globalsCleanup` mode when one was explicitly configured, and follow the mode that is actually in effect ([#&#8203;16323](https://redirect.github.com/jestjs/jest/pull/16323))
- `[jest-environment-node, jest-util]` Stop resolving lazy globals when setting up an environment, so Node 26's builtin module globals are no longer loaded (and no longer emit their deprecation warnings) for every test file ([#&#8203;16324](https://redirect.github.com/jestjs/jest/pull/16324))
- `[jest-haste-map]` Keep watch mode alive when an outside process briefly makes a file unreadable on Windows, instead of tearing the watcher down on `EPERM` ([#&#8203;16295](https://redirect.github.com/jestjs/jest/pull/16295))
- `[jest-haste-map]` Keep indexing when an outside process holds a file open on Windows, instead of failing the whole crawl on `EPERM` ([#&#8203;16358](https://redirect.github.com/jestjs/jest/pull/16358))
- `[jest-haste-map]` Keep a duplicated manual mock resolving when the file it pointed at is deleted in watch mode ([#&#8203;16360](https://redirect.github.com/jestjs/jest/pull/16360))
- `[jest-haste-map]` Shut the worker farm down when a duplicate manual mock aborts the build under `throwOnModuleCollision` ([#&#8203;16354](https://redirect.github.com/jestjs/jest/pull/16354))
- `[jest-haste-map]` Attach the watchman client's `error` listener before the first command, so a watchman failure falls back to the node crawler instead of crashing on an unhandled `error` event, and always end the client ([#&#8203;16355](https://redirect.github.com/jestjs/jest/pull/16355))
- `[jest-haste-map]` Stop delivering watch events after `WatchmanWatcher` is closed, and route its warnings through the configured console ([#&#8203;16355](https://redirect.github.com/jestjs/jest/pull/16355))
- `[jest-haste-map]` Restore the nested `duplicates` index correctly in `ModuleMap.fromJSON`, so a haste collision reported inside a test worker raises `DuplicateHasteCandidatesError` instead of a `TypeError` ([#&#8203;16353](https://redirect.github.com/jestjs/jest/pull/16353))
- `[jest-haste-map]` Match watched files on a full extension, so `moduleFileExtensions: ['js']` no longer accepts `foo.mjs` ([#&#8203;16352](https://redirect.github.com/jestjs/jest/pull/16352))
- `[jest-haste-map]` Delimit the fields that make up the haste map cache key, so two different option sets cannot hash to the same cache file ([#&#8203;16352](https://redirect.github.com/jestjs/jest/pull/16352))
- `[jest-message-util]` Print the inner errors of an `AggregateError` thrown inside a test ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-message-util]` Indent nested `cause` and `AggregateError` sections of a test failure by one level per depth, so the nesting is legible instead of rendering flat ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-message-util]` Color stack traces line by line so blank lines stay blank ([#&#8203;16316](https://redirect.github.com/jestjs/jest/pull/16316))
- `[jest-message-util]` Detect Jest's own frames without assuming the checkout directory's name, and cover `@jest/*` packages, so stack traces and code frames point at user code ([#&#8203;16326](https://redirect.github.com/jestjs/jest/pull/16326))
- `[jest-mock]` `mockResolvedValue` / `mockRejectedValue` now see all overload return types, so a Promise-returning overload survives even when a later overload returns a non-Promise (e.g. `pg.Client['end']`) ([#&#8203;16237](https://redirect.github.com/jestjs/jest/pull/16237))
- `[@jest-environment/jsdom-abstract]` Make `@types/jsdom` a peer dependency ([#&#8203;16166](https://redirect.github.com/jestjs/jest/pull/16166))
- `[jest-mock]` Remove the leftover own accessor descriptor when restoring a `spyOn` of an inherited getter or setter, so the instance keeps reflecting the prototype ([#&#8203;16226](https://redirect.github.com/jestjs/jest/pull/16226))
- `[jest-resolve]` Include `extensionsToTreatAsEsm` in the `shouldLoadAsEsm` cache key, so projects with different extension lists don't read each other's answers ([#&#8203;16369](https://redirect.github.com/jestjs/jest/pull/16369))
- `[jest-resolve]` Make `getModuleIDAsync` build and cache `data:` URI module IDs the same way as `getModuleID` ([#&#8203;16370](https://redirect.github.com/jestjs/jest/pull/16370))
- `[jest-resolve]` Keep the `node:` prefix when resolving a core module asynchronously, so a builtin that only exists prefixed (`node:sea`, `node:sqlite`, `node:test`, `node:test/reporters`) resolves instead of failing as a missing bare package ([#&#8203;16388](https://redirect.github.com/jestjs/jest/pull/16388))
- `[jest-resolve]` Look up manual mocks for `node:` protocol specifiers under the unprefixed name they are stored as ([#&#8203;16388](https://redirect.github.com/jestjs/jest/pull/16388))
- `[jest-resolve]` Apply `moduleNameMapper` consistently to both spellings of core module specifiers (`fs` vs `node:fs`) ([#&#8203;16390](https://redirect.github.com/jestjs/jest/pull/16390))
- `[jest-resolve]` Keep virtual and ordinary mock module IDs isolated across test files ([#&#8203;16296](https://redirect.github.com/jestjs/jest/pull/16296))
- `[jest-resolve]` Guard missing `require.resolve.paths` ([#&#8203;16052](https://redirect.github.com/jestjs/jest/pull/16052))
- `[jest-resolve, jest-config, jest-runner]` Support a user resolver written as an ES module ([#&#8203;16332](https://redirect.github.com/jestjs/jest/pull/16332))
- `[jest-resolve, jest-runtime]` Throw the CJS parse error for ESM syntax in a `"type": "commonjs"` package or a `.cjs` file instead of loading it as ESM, matching Node ([#&#8203;16368](https://redirect.github.com/jestjs/jest/pull/16368))
- `[@jest/source-map]` Keep source map sources that name a scheme, such as `webpack:///`, instead of resolving them into a path that does not exist ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[@jest/source-map]` Look up `--testLocationInResults` positions at the right column, and keep a mapping to the first column instead of discarding it ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[@jest/source-map]` Warn when a source map cannot be parsed, instead of silently leaving its frames untranslated ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[jest-runner, @jest/source-map]` Keep a source-mapped stack for an error thrown after the test environment was torn down ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[jest-runtime, @jest/source-map]` Keep source maps past teardown and past the next test file's `install`, so a stack from a file no earlier stack mentioned still points at the original source ([#&#8203;16330](https://redirect.github.com/jestjs/jest/pull/16330))
- `[jest-runtime]` Report that no coverage was collected when `getAllV8CoverageInfoCopy` is called after `teardown`, instead of returning an empty result ([#&#8203;16385](https://redirect.github.com/jestjs/jest/pull/16385))
- `[jest-runtime]` Cache a CJS module's parsed exports before walking its re-exports, so two modules that re-export each other no longer overflow the stack when imported from ESM ([#&#8203;16363](https://redirect.github.com/jestjs/jest/pull/16363))
- `[jest-runtime]` Keep a re-exported ES module's parse failure from marking the re-exporting CommonJS file as ESM, so `module.exports = require('./dep.mjs')` loads instead of failing with `module is not defined` ([#&#8203;16363](https://redirect.github.com/jestjs/jest/pull/16363))
- `[jest-runtime]` Scope module mocks instantiated inside `jest.isolateModules`/`isolateModulesAsync` to that block, so a mock first imported there no longer outlives it - matching how CommonJS mocks already behave ([#&#8203;16365](https://redirect.github.com/jestjs/jest/pull/16365))
- `[jest-runtime]` Suspend module isolation while generating an automock, so loading the real module to read its shape no longer populates the isolated registry ([#&#8203;16365](https://redirect.github.com/jestjs/jest/pull/16365))
- `[jest-runtime]` Check a cached ES module's status before `require()` returns it, so a module whose evaluation threw rethrows that error and one left linked by a failed sibling is evaluated instead of returning uninitialized bindings ([#&#8203;16364](https://redirect.github.com/jestjs/jest/pull/16364))
- `[jest-runtime]` Report the original `ERR_REQUIRE_ASYNC_MODULE` when a `require()` of a top-level-await graph is retried, instead of a spurious "concurrent `import()`" error ([#&#8203;16364](https://redirect.github.com/jestjs/jest/pull/16364))
- `[jest-runtime]` Throw the evaluation error when another caller's `import()` of the same module failed while we awaited it, instead of resolving with the errored module ([#&#8203;16364](https://redirect.github.com/jestjs/jest/pull/16364))
- `[jest-runtime]` Mark the result of `require()`ing an ES module that has a default export with `__esModule: true` through a live-binding facade, and serve the same object from `require.cache`, matching Node ([#&#8203;16367](https://redirect.github.com/jestjs/jest/pull/16367))
- `[jest-runtime]` Provide a CommonJS module's exports under the `'module.exports'` named export when imported from ESM, matching Node 23+ ([#&#8203;16367](https://redirect.github.com/jestjs/jest/pull/16367))
- `[jest-runtime]` Give the test file itself a non-null `require.main` ([#&#8203;16367](https://redirect.github.com/jestjs/jest/pull/16367))
- `[jest-runtime]` Populate `module.children` with the modules a file loads, matching Node ([#&#8203;16368](https://redirect.github.com/jestjs/jest/pull/16368))
- `[jest-runtime]` Provide `import.meta.resolve` and `import.meta.jest` in `data:` URI modules, accept any-case mediatype parameters, and use Node's error codes for invalid `data:` URIs ([#&#8203;16368](https://redirect.github.com/jestjs/jest/pull/16368))
- `[jest-runtime]` Key ES modules by full URL, so query and fragment suffixes create the same module instances as Node and show up in `import.meta.url` ([#&#8203;16375](https://redirect.github.com/jestjs/jest/pull/16375))
- `[jest-runtime]` Share modules between overlapping graphs when a CommonJS module `require()`s an ES module mid-load, instead of evaluating shared dependencies twice ([#&#8203;16375](https://redirect.github.com/jestjs/jest/pull/16375))
- `[jest-runtime]` Throw `ERR_REQUIRE_CYCLE_MODULE` like Node when a CommonJS module `require()`s an ES module that is still being loaded, instead of evaluating the module a second time ([#&#8203;16366](https://redirect.github.com/jestjs/jest/pull/16366))
- `[jest-runtime]` Key builtin modules in the ESM registry by one canonical specifier ([#&#8203;16341](https://redirect.github.com/jestjs/jest/pull/16341))
- `[jest-runtime]` `import.meta.resolve()` for a builtin uses its `node:` specifier ([#&#8203;16341](https://redirect.github.com/jestjs/jest/pull/16341))
- `[jest-runtime]` Fall back to native ESM when a `.js` file contains ESM syntax but has no `"type":"module"` marker ([#&#8203;16152](https://redirect.github.com/jestjs/jest/pull/16152))
- `[jest-runtime]` Allow `require()` of ESM-marked files on Node < 24.9 via transform fallback ([#&#8203;16244](https://redirect.github.com/jestjs/jest/pull/16244))
- `[jest-runtime, @jest/transform]` Surface actionable `ERR_REQUIRE_ESM` error for files with untransformed ESM syntax instead of the generic "unexpected token" message ([#&#8203;16244](https://redirect.github.com/jestjs/jest/pull/16244))
- `[jest-runtime]` Support older test environments whose `moduleMocker` does not implement `clearMocksOnScope` ([#&#8203;16169](https://redirect.github.com/jestjs/jest/pull/16169))
- `[jest-runtime]` Apply `jest.unstable_mockModule` when the mocked file itself is `require()`d, not only when it is imported as a dependency ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Apply `jest.unstable_mockModule` to statically imported `data:` URIs on Node 24.9+, matching dynamic `import()` ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Run an async `jest.unstable_mockModule` factory once per module instead of twice, and fail the import instead of crashing the worker when the factory rejects ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Hide a `require(esm)` module that failed to evaluate from `require.cache`, as Node does, instead of exposing a namespace with uninitialized bindings ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Strip the byte-order mark when importing a JSON module, matching `require()` and Node ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Throw `ERR_REQUIRE_ASYNC_MODULE` when `require(esm)` runs under an async-only custom resolver, instead of silently resolving with the default resolver ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Parse imported JSON modules with the test realm's `JSON`, so their objects pass `instanceof Object` inside the test like `require()`d JSON does ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Accept every `file:` URL string in the sandboxed `module.createRequire`, including one with a `localhost` authority, as Node does ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-runtime]` Point at `{virtual: true}` when `jest.mock` or `jest.unstable_mockModule` is given a module that cannot be resolved ([#&#8203;16389](https://redirect.github.com/jestjs/jest/pull/16389))
- `[jest-reporters]` Fix coverage report table formatting in CI/GitHub Actions environments where `process.stdout.columns` is undefined by falling back to the `COLUMNS` env var or `80` columns in CI, preserving existing behaviour in other non-TTY environments ([#&#8203;16227](https://redirect.github.com/jestjs/jest/pull/16227))
- `[jest-runtime]` Support CJS-in-ESM exports via `"module.exports"` named exports ([#&#8203;16277](https://redirect.github.com/jestjs/jest/pull/16277))
- `[jest-snapshot]` Keep a skipped or failed test's hinted snapshots, instead of reporting them obsolete ([#&#8203;16348](https://redirect.github.com/jestjs/jest/pull/16348))
- `[jest-util]` Stop `globsToMatcher` reusing a cached matcher compiled with different picomatch options, and keep its `dot: true` default when `dot` is passed as `undefined` ([#&#8203;16381](https://redirect.github.com/jestjs/jest/pull/16381))
- `[pretty-format]` Move the `react-is` aliases into the `@jest` scope, so they cannot be shadowed by unrelated packages published under the alias names ([#&#8203;16333](https://redirect.github.com/jestjs/jest/pull/16333))

##### Chore & Maintenance

- `[docs]` Document the intentional divergences from Node's module system in the ECMAScript Modules page ([#&#8203;16368](https://redirect.github.com/jestjs/jest/pull/16368))
- `[docs]` Note deprecation of `react-test-renderer` in React Native tutorial and `pretty-format` README ([#&#8203;16294](https://redirect.github.com/jestjs/jest/pull/16294))
- `[docs]` Use `@testing-library/react-native` in the React Native tutorial instead of the deprecated `react-test-renderer` ([#&#8203;16318](https://redirect.github.com/jestjs/jest/pull/16318))
- `[babel-jest, @jest/transform]` Update `babel-plugin-istanbul` to v8 ([#&#8203;16049](https://redirect.github.com/jestjs/jest/pull/16049))
- `[jest-config, @jest/reporters, jest-runtime]` Update `glob` to v13 ([#&#8203;16397](https://redirect.github.com/jestjs/jest/pull/16397))
- `[jest-haste-map]` Refactor massive class into multiple files ([#&#8203;16180](https://redirect.github.com/jestjs/jest/pull/16180))
- `[jest-haste-map]` Drop `walker` dependency; replace hand-rolled directory recursion in the JS crawler and watcher startup with `fdir` ([#&#8203;16187](https://redirect.github.com/jestjs/jest/pull/16187))
- `[jest-haste-map]` Reuse cached metadata for files whose haste name is a known duplicate, instead of re-reading and re-parsing them on every startup ([#&#8203;16351](https://redirect.github.com/jestjs/jest/pull/16351))
- `[jest-haste-map]` Cache the watchman socket path and replace the `watchman --version` probe with `get-sockname`, so warm runs spawn no watchman processes ([#&#8203;16386](https://redirect.github.com/jestjs/jest/pull/16386))
- `[jest-resolve]` Store the per-directory package-type lookup in the cache it reads, so it actually memoizes ([#&#8203;16369](https://redirect.github.com/jestjs/jest/pull/16369))
- `[jest-resolve, jest-runtime]` Cut repeated work on the resolution hot path: hoist the platform-extension list to construction, memoize `isCoreModule` and the options cache-key serialization, skip mapper preparation when no `moduleNameMapper` is configured, run each mapper regex once, and stop re-parsing `NODE_OPTIONS` on every default-resolver call ([#&#8203;16371](https://redirect.github.com/jestjs/jest/pull/16371))
- `[jest-resolve]` Cut warm resolution cost to about a third: reuse one `unrs-resolver` factory per options shape instead of cloning per resolution, compose the factory cache key from per-array cached strings instead of serializing options, and stop constructing an `Error` for misses that `findNodeModule` swallows; add a `__benchmarks__` suite for the default resolver ([#&#8203;16373](https://redirect.github.com/jestjs/jest/pull/16373))
- `[jest-runner, @jest/source-map]` Replace `source-map-support` with an implementation in `@jest/source-map` ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[jest-snapshot]` Load babel, semver and synckit lazily, so requiring the package (which every test process does through `@jest/expect`) no longer loads \~200 modules that only writing inline snapshots needs ([#&#8203;16387](https://redirect.github.com/jestjs/jest/pull/16387))
- `[jest-runtime]` Reduce per-require overhead: skip module ID resolution when no mock can apply, answer core modules before probing for a manual mock, share one `require.cache` proxy across modules, and cache empty files ([#&#8203;16376](https://redirect.github.com/jestjs/jest/pull/16376))
- `[@jest/source-map]` Deprecate `getCallsite` in favour of `SourceMapSupport#getCallsite` ([#&#8203;16327](https://redirect.github.com/jestjs/jest/pull/16327))
- `[jest-runtime]` Avoid magical `null` value in ESM loader ([#&#8203;16160](https://redirect.github.com/jestjs/jest/pull/16160))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42MS4zIiwidXBkYXRlZEluVmVyIjoiNDQuNjEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-08 08:17:22 -07:00
renovate[bot] 8557eff8db chore(deps): update lint (#694)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [oxfmt](https://oxc.rs/docs/guide/usage/formatter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt)) | [`0.63.0` → `0.66.0`](https://renovatebot.com/diffs/npm/oxfmt/0.63.0/0.66.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxfmt/0.66.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxfmt/0.63.0/0.66.0?slim=true) |
| [oxlint](https://oxc.rs/docs/guide/usage/linter) ([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint)) | [`1.78.0` → `1.81.0`](https://renovatebot.com/diffs/npm/oxlint/1.78.0/1.81.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.81.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.78.0/1.81.0?slim=true) |
| [oxlint-config-universe](https://redirect.github.com/expo/oxlint-config-universe) | [`0.0.3` → `0.2.0`](https://renovatebot.com/diffs/npm/oxlint-config-universe/0.0.3/0.2.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint-config-universe/0.2.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint-config-universe/0.0.3/0.2.0?slim=true) |

---

### Release Notes

<details>
<summary>oxc-project/oxc (oxfmt)</summary>

### [`v0.66.0`](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.65.0...oxfmt_v0.66.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.65.0...oxfmt_v0.66.0)

### [`v0.65.0`](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.64.0...oxfmt_v0.65.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.64.0...oxfmt_v0.65.0)

### [`v0.64.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxfmt/CHANGELOG.md#0640---2026-08-18)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxfmt_v0.63.0...oxfmt_v0.64.0)

##### 🚀 Features

- [`c07fe7c`](https://redirect.github.com/oxc-project/oxc/commit/c07fe7c) oxfmt: Support `experimentalOperatorPosition` ([#&#8203;25643](https://redirect.github.com/oxc-project/oxc/issues/25643)) (leaysgur)

##### 📚 Documentation

- [`fed6681`](https://redirect.github.com/oxc-project/oxc/commit/fed6681) oxfmt: Skip expanding overrides options ([#&#8203;25572](https://redirect.github.com/oxc-project/oxc/issues/25572)) (leaysgur)

</details>

<details>
<summary>oxc-project/oxc (oxlint)</summary>

### [`v1.81.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1810---2026-08-31)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.80.0...oxlint_v1.81.0)

##### 📚 Documentation

- [`d5be037`](https://redirect.github.com/oxc-project/oxc/commit/d5be037) linter/typescript/switch-exhaustiveness-check: Clarify default case comment pattern ([#&#8203;26100](https://redirect.github.com/oxc-project/oxc/issues/26100)) (camc314)

### [`v1.80.0`](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.79.0...oxlint_v1.80.0)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.79.0...oxlint_v1.80.0)

### [`v1.79.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1790---2026-08-18)

[Compare Source](https://redirect.github.com/oxc-project/oxc/compare/oxlint_v1.78.0...oxlint_v1.79.0)

##### 💥 BREAKING CHANGES

- [`8c4552d`](https://redirect.github.com/oxc-project/oxc/commit/8c4552d) linter: \[**BREAKING**] Split react/react-compiler into per-category rules ([#&#8203;25500](https://redirect.github.com/oxc-project/oxc/issues/25500)) (Boshen)

##### 🐛 Bug Fixes

- [`228e8e0`](https://redirect.github.com/oxc-project/oxc/commit/228e8e0) linter: Resolve inactive React compiler rules ([#&#8203;25830](https://redirect.github.com/oxc-project/oxc/issues/25830)) (Boshen)
- [`aa49d86`](https://redirect.github.com/oxc-project/oxc/commit/aa49d86) linter: Allow spread rule options in config types ([#&#8203;25675](https://redirect.github.com/oxc-project/oxc/issues/25675)) (ch3rry)
- [`36f8451`](https://redirect.github.com/oxc-project/oxc/commit/36f8451) linter/eslint/no-eval: Align indirect default with ESLint ([#&#8203;25656](https://redirect.github.com/oxc-project/oxc/issues/25656)) (camc314)
- [`beb724d`](https://redirect.github.com/oxc-project/oxc/commit/beb724d) linter/eslint/no-unused-vars: Report bare underscore parameters ([#&#8203;25663](https://redirect.github.com/oxc-project/oxc/issues/25663)) (camc314)
- [`4004c10`](https://redirect.github.com/oxc-project/oxc/commit/4004c10) linter/eslint/no-irregular-whitespace: Check comments by default ([#&#8203;25660](https://redirect.github.com/oxc-project/oxc/issues/25660)) (camc314)
- [`285820e`](https://redirect.github.com/oxc-project/oxc/commit/285820e) linter/no-large-snapshots: Precompile and document allowed snapshot matchers ([#&#8203;25611](https://redirect.github.com/oxc-project/oxc/issues/25611)) (Mikhail Baev)
- [`4df5835`](https://redirect.github.com/oxc-project/oxc/commit/4df5835) linter: Allow capitalized built-in calls ([#&#8203;25516](https://redirect.github.com/oxc-project/oxc/issues/25516)) (Boshen)

</details>

<details>
<summary>expo/oxlint-config-universe (oxlint-config-universe)</summary>

### [`v0.2.0`](https://redirect.github.com/expo/oxlint-config-universe/releases/tag/v0.2.0)

[Compare Source](https://redirect.github.com/expo/oxlint-config-universe/compare/a082d4f808eaea495b5f62b5bad4342d69de7442...v0.2.0)

#### What's Changed

- fix: fill in some documented gaps with rules that exist by [@&#8203;wschurman](https://redirect.github.com/wschurman) in [#&#8203;1](https://redirect.github.com/expo/oxlint-config-universe/pull/1)
- chore: explicitly enable some rules that are on by default by [@&#8203;wschurman](https://redirect.github.com/wschurman) in [#&#8203;2](https://redirect.github.com/expo/oxlint-config-universe/pull/2)
- fix: re-enable no-control-regex by [@&#8203;wschurman](https://redirect.github.com/wschurman) in [#&#8203;3](https://redirect.github.com/expo/oxlint-config-universe/pull/3)
- chore: explicitly enable more rules that are on by default by [@&#8203;wschurman](https://redirect.github.com/wschurman) in [#&#8203;4](https://redirect.github.com/expo/oxlint-config-universe/pull/4)
- chore: bump version, remove manual changelog for now by [@&#8203;wschurman](https://redirect.github.com/wschurman) in [#&#8203;5](https://redirect.github.com/expo/oxlint-config-universe/pull/5)
- fix: support oxlint 1.79 by [@&#8203;hirbod](https://redirect.github.com/hirbod) in [#&#8203;6](https://redirect.github.com/expo/oxlint-config-universe/pull/6)
- chore: modernize tooling — Yarn 4, generated types, npm trusted publishing by [@&#8203;hirbod](https://redirect.github.com/hirbod) in [#&#8203;7](https://redirect.github.com/expo/oxlint-config-universe/pull/7)
- fix: push version commit with bot PAT in publish workflow by [@&#8203;hirbod](https://redirect.github.com/hirbod) in [#&#8203;8](https://redirect.github.com/expo/oxlint-config-universe/pull/8)
- chore: drop production environment from publish workflow by [@&#8203;hirbod](https://redirect.github.com/hirbod) in [#&#8203;9](https://redirect.github.com/expo/oxlint-config-universe/pull/9)

#### New Contributors

- [@&#8203;wschurman](https://redirect.github.com/wschurman) made their first contribution in [#&#8203;1](https://redirect.github.com/expo/oxlint-config-universe/pull/1)
- [@&#8203;hirbod](https://redirect.github.com/hirbod) made their first contribution in [#&#8203;6](https://redirect.github.com/expo/oxlint-config-universe/pull/6)

**Full Changelog**: <https://github.com/expo/oxlint-config-universe/commits/v0.2.0>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42MS4zIiwidXBkYXRlZEluVmVyIjoiNDQuNjEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-08 08:17:12 -07:00
renovate[bot] ba3ba9ea89 chore(deps): update dependency jdx/mise to v2026.9.2 (#692)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jdx/mise](https://redirect.github.com/jdx/mise) | minor | `v2026.8.15` → `v2026.9.2` |

---

### Release Notes

<details>
<summary>jdx/mise (jdx/mise)</summary>

### [`v2026.9.2`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.2): : Packslip Backend, SSH Relay, and Reimagined Install Progress

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.1...vfox-v2026.9.2)

This is a large release headlined by the new stable `packslip:` backend for installing tools from a vendor's own signed release manifest, a read-only GitHub SSH relay for remote onboarding, redesigned install progress for both terminals and CI logs, and a rebuilt dotfiles-tracking model for bootstrap. It also carries dozens of fixes across shims, tasks, brew, Go, npm, and the schema, plus two security fixes.

##### Highlights

- The `packslip:` backend is now generally available: install tools directly from a project's cryptographically signed release manifest, with signer pinning, trusted stampers, host-requirement checks, shell completions, and agent skills all driven from the same signed source.
- Install output was reimagined end to end. Interactive terminals get a live region with a fractional install-wide bar and permanent completion lines, while CI logs, pipes, and AI-agent terminals get a compact append-only reporter instead of thousands of scrolling rows.
- Bootstrap grows a read-only GitHub SSH relay and `--from-git` onboarding, an AUR package manager, and a rebuilt dotfiles-history model that tracks files through ordinary Git commits synchronized with an origin.

##### Added

- **packslip:** New `packslip:` backend installs tools from a vendor-published, signed release manifest (a sigstore bundle) that names every artifact with its digest, platform, format, and executables. The tool name is a pin, like a `known_hosts` entry: `packslip:github.com/owner/repo` accepts only a packslip signed by that repository's release workflow, and signature, log entry, statement, digest, and size are all verified before anything is unpacked. Custom hosts and monorepo tools are supported. The backend is no longer experimental. ([#&#8203;12778](https://redirect.github.com/jdx/mise/pull/12778), [#&#8203;12811](https://redirect.github.com/jdx/mise/pull/12811) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools]
  "packslip:github.com/oxc-project/oxc/oxlint" = "latest"
  ```
- **packslip:** Signer pinning remembers, per project, the signer that signed the first accepted release in a `pins.toml` file (like SSH's known\_hosts) and records the signer in `mise.lock`, so a later release signed by anyone else, or one that weakens provenance, is refused. `mise packslip pins` lists what is pinned and `mise packslip forget <project>` accepts an announced key rotation. ([#&#8203;12783](https://redirect.github.com/jdx/mise/pull/12783) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** New `packslip.stampers` setting lets you require that a trusted host has stamped a version before mise offers or installs it (a scanning service, a mirror, or eventually the registry), with a per-tool `trust = "vendor"` override. ([#&#8203;12782](https://redirect.github.com/jdx/mise/pull/12782) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** Declared host requirements (OS/glibc minimums, shared libraries, required commands) are checked before download; confirmed failures refuse the install, gaps mise cannot verify warn only, and `ignore_requirements = true` overrides hard failures. `latest` also resolves from verified vendor recommendations. ([#&#8203;12804](https://redirect.github.com/jdx/mise/pull/12804), [#&#8203;12805](https://redirect.github.com/jdx/mise/pull/12805) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **packslip:** A tool's packslip can ship shell completions and agent skills. `mise completion <shell> --tool <name>` prints (or `--install` writes a version-aware stub for) completions for whichever version is active in the current directory, and `mise skills ls` / `mise skills sync` link a tool's Agent Skills into `.claude/skills` at the pinned version. Completions are also loaded automatically in activated shells. ([#&#8203;12779](https://redirect.github.com/jdx/mise/pull/12779), [#&#8203;12780](https://redirect.github.com/jdx/mise/pull/12780), [#&#8203;12848](https://redirect.github.com/jdx/mise/pull/12848) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** Redesigned install progress. Interactive terminals now show a live region with an install-wide fractional bar, per-tool phase/elapsed/artifact rows, dependency-wait rows ("waiting for node\@&#8203;24.20.0"), and permanent completion lines written into scrollback. `prune`, `uninstall`, and upgrade removals reuse the same session, so pruning hundreds of versions no longer floods the screen. ([#&#8203;12906](https://redirect.github.com/jdx/mise/pull/12906) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** CI logs, pipes, and AI-agent terminals (`CLAUDECODE`/`AI_AGENT`) get a compact append-only reporter: one permanent line per finished tool plus a periodic snapshot of the bar, active phases, transfer rates, and queue count, instead of hundreds of scrolling status rows. Resolver hosts and retry progress are surfaced too. ([#&#8203;12902](https://redirect.github.com/jdx/mise/pull/12902), [#&#8203;12907](https://redirect.github.com/jdx/mise/pull/12907) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **ssh:** New `mise ssh` runs ordinary OpenSSH sessions with optional session-scoped, read-only GitHub access, and `mise bootstrap remote --from-git` onboards a remote host from a Git repo. The initiating machine keeps the credential and brokers authorized clone/fetch and REST reads over an SSH Unix-socket forward; no token is installed on the target. ([#&#8203;12830](https://redirect.github.com/jdx/mise/pull/12830) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise ssh devbox --github-relay-read-only --github-relay-repo jdx/dotfiles
  ```
- **bootstrap:** `mise bootstrap --from-git <GIT_URL>` clones a repository-backed global config into `$MISE_CONFIG_DIR` so its `config.toml`, `conf.d/`, and tasks apply on the first bootstrap and stay active afterward. ([#&#8203;12715](https://redirect.github.com/jdx/mise/pull/12715) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** New `aur:` package manager installs from the Arch User Repository via `yay` (preferred) or `paru`, with foreign-package status detection so repo packages with colliding names cannot satisfy an AUR request. Bootstrap can also declare packages as absent to remove them, and now reports which root declarations were selected. ([#&#8203;12718](https://redirect.github.com/jdx/mise/pull/12718), [#&#8203;12716](https://redirect.github.com/jdx/mise/pull/12716), [#&#8203;12770](https://redirect.github.com/jdx/mise/pull/12770) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Rebuilt dotfiles tracking. Files enrolled with `mode = "track"` stay in place while mise commits changes to a separate bare Git store with checkpoints, rollback/undo, and optional origin synchronization, including encryption of shared files before storage. ([#&#8203;12918](https://redirect.github.com/jdx/mise/pull/12918) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **activate:** New `activate_shims = false` (`MISE_ACTIVATE_SHIMS=false`) keeps tool shim directories off PATH during activation and hooks without changing your auto-install or lazy-tool settings. Command wrappers such as mr-boxington's `cargo` keep working. ([#&#8203;12926](https://redirect.github.com/jdx/mise/pull/12926) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **core:** Rust tools accept `mr_boxington = true` and `mise use` gains a repeatable `--tool-option KEY=VALUE` flag, so setting up the Cargo wrapper is a single command. ([#&#8203;12908](https://redirect.github.com/jdx/mise/pull/12908) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  mise use --tool-option mr_boxington=true rust mr-boxington
  ```
- **ruby:** mise now reads Bundler's `ruby file: ".ruby-version"` form from a `Gemfile` (resolving the path next to the Gemfile), so projects that pin Ruby through a sibling file resolve correctly. ([#&#8203;12914](https://redirect.github.com/jdx/mise/pull/12914) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **self-update:** New global-only `self_update.repository` and `self_update.api_url` settings let organizations point manual updates, out-of-date hints, and automatic updates at a curated GitHub or GitHub Enterprise release mirror. Project config cannot redirect updates, and artifacts still pass the embedded-signature check. ([#&#8203;12735](https://redirect.github.com/jdx/mise/pull/12735) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** `install_env` is now applied when resolving and downloading tools, not just at build time. ([#&#8203;12777](https://redirect.github.com/jdx/mise/pull/12777) by [@&#8203;rabadin](https://redirect.github.com/rabadin))
- **brew:** mise can now evaluate ordinary third-party taps. ([#&#8203;12774](https://redirect.github.com/jdx/mise/pull/12774) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Fixed

- **shim:** Lazy tool names now match on Windows, and `mise-shim.exe` resolves correctly through a symlinked `mise.exe`. ([#&#8203;12699](https://redirect.github.com/jdx/mise/pull/12699) by [@&#8203;jdx](https://redirect.github.com/jdx), [#&#8203;12915](https://redirect.github.com/jdx/mise/pull/12915) by [@&#8203;acooler15](https://redirect.github.com/acooler15))
- **activate:** Paths are translated for the calling Windows POSIX shell, and PowerShell pipeline input is now forwarded. ([#&#8203;12736](https://redirect.github.com/jdx/mise/pull/12736), [#&#8203;12925](https://redirect.github.com/jdx/mise/pull/12925) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **nushell:** The activation prelude preserves the PATH list type. ([#&#8203;12754](https://redirect.github.com/jdx/mise/pull/12754) by [@&#8203;Junaid-PK](https://redirect.github.com/Junaid-PK))
- **task:** Task globs no longer loop on symlinks, `sources` accepts a single string, task status is preserved when the cache audit tracer fails, and POSIX shells no longer have PATH pre-converted (the shell already does it). ([#&#8203;12711](https://redirect.github.com/jdx/mise/pull/12711), [#&#8203;12769](https://redirect.github.com/jdx/mise/pull/12769) by [@&#8203;jdx](https://redirect.github.com/jdx), [#&#8203;12530](https://redirect.github.com/jdx/mise/pull/12530) by [@&#8203;risu729](https://redirect.github.com/risu729), [#&#8203;12696](https://redirect.github.com/jdx/mise/pull/12696) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **npm:** Global installs now work with pnpm 12. ([#&#8203;12717](https://redirect.github.com/jdx/mise/pull/12717) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **go:** Private modules resolve via Go, and loong64 uses the correct artifact architecture. ([#&#8203;12731](https://redirect.github.com/jdx/mise/pull/12731) by [@&#8203;kejne](https://redirect.github.com/kejne), [#&#8203;12767](https://redirect.github.com/jdx/mise/pull/12767) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew:** A tapped install now explains why it found no API metadata, and all tap formula layouts are supported. brew-cask packages with `auto_updates` enabled are upgraded correctly. ([#&#8203;12645](https://redirect.github.com/jdx/mise/pull/12645) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743), [#&#8203;12837](https://redirect.github.com/jdx/mise/pull/12837) by [@&#8203;soodoh](https://redirect.github.com/soodoh), [#&#8203;12857](https://redirect.github.com/jdx/mise/pull/12857) by [@&#8203;himkt](https://redirect.github.com/himkt))
- **github:** Releases with no assets are omitted from the version list, and OAuth-refresh warnings are silenced during env export and shown only once when a refresh is rejected. ([#&#8203;12572](https://redirect.github.com/jdx/mise/pull/12572) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743), [#&#8203;12855](https://redirect.github.com/jdx/mise/pull/12855), [#&#8203;12911](https://redirect.github.com/jdx/mise/pull/12911) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** Every newly added `.tool-versions` line is now terminated with a newline. ([#&#8203;12740](https://redirect.github.com/jdx/mise/pull/12740) by [@&#8203;dylanpulver](https://redirect.github.com/dylanpulver))
- **env:** Bootstrap shims for lazy tools are created when printing the env, and duplicate unset directives are removed from hook output. ([#&#8203;12726](https://redirect.github.com/jdx/mise/pull/12726) by [@&#8203;hktitof](https://redirect.github.com/hktitof), [#&#8203;12707](https://redirect.github.com/jdx/mise/pull/12707) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **http:** A failed download's partial file is flushed before giving up, and cache compression finishes before files are published. ([#&#8203;12823](https://redirect.github.com/jdx/mise/pull/12823) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743), [#&#8203;12894](https://redirect.github.com/jdx/mise/pull/12894) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Tera templates render in hooks, tildes expand in managed file sources, and the relative config dir is pinned during reexec. ([#&#8203;12727](https://redirect.github.com/jdx/mise/pull/12727), [#&#8203;12768](https://redirect.github.com/jdx/mise/pull/12768), [#&#8203;12721](https://redirect.github.com/jdx/mise/pull/12721) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** Disabled plugins are excluded from shorthand resolution, and installed plugins are included in plugin completion. ([#&#8203;12927](https://redirect.github.com/jdx/mise/pull/12927), [#&#8203;12720](https://redirect.github.com/jdx/mise/pull/12720) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **rust:** Incomplete rustup toolchains are reconciled. ([#&#8203;12771](https://redirect.github.com/jdx/mise/pull/12771) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **oci/vfox/aqua:** OCI reused tool layers preserve their paths, vfox preserves the tool name from legacy lockfiles, and aqua handles repository transfers in attestations. ([#&#8203;12749](https://redirect.github.com/jdx/mise/pull/12749), [#&#8203;12745](https://redirect.github.com/jdx/mise/pull/12745), [#&#8203;12766](https://redirect.github.com/jdx/mise/pull/12766) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **sandbox:** `SYS_fork`/`SYS_vfork` are omitted on aarch64. ([#&#8203;12807](https://redirect.github.com/jdx/mise/pull/12807) by [@&#8203;jamescassell](https://redirect.github.com/jamescassell))
- **dotfiles:** A stale directory the deeper walk already removed is tolerated. ([#&#8203;12697](https://redirect.github.com/jdx/mise/pull/12697) by [@&#8203;vladbisceanu](https://redirect.github.com/vladbisceanu))
- **schema:** Absent bootstrap packages, inline dotfile content, and missing built-in deps providers are now modeled. ([#&#8203;12785](https://redirect.github.com/jdx/mise/pull/12785) by [@&#8203;nettlesh](https://redirect.github.com/nettlesh), [#&#8203;12738](https://redirect.github.com/jdx/mise/pull/12738), [#&#8203;12739](https://redirect.github.com/jdx/mise/pull/12739) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **lockfile:** The deferred provenance baseline is dropped once auto-lock verifies the upgrade. ([#&#8203;12825](https://redirect.github.com/jdx/mise/pull/12825) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Security

- **http:** URL replacements no longer forward authorization headers or URL credentials when an HTTPS origin is rewritten to HTTP; such credential-carrying downgrades are refused, and errors omit URLs so embedded credentials cannot leak through diagnostics. ([#&#8203;12879](https://redirect.github.com/jdx/mise/pull/12879) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **self-update:** Self-update now requires secure release sources. ([#&#8203;12737](https://redirect.github.com/jdx/mise/pull/12737) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### New Contributors

- [@&#8203;acooler15](https://redirect.github.com/acooler15) made their first contribution in [#&#8203;12915](https://redirect.github.com/jdx/mise/pull/12915)
- [@&#8203;soodoh](https://redirect.github.com/soodoh) made their first contribution in [#&#8203;12837](https://redirect.github.com/jdx/mise/pull/12837)
- [@&#8203;jamescassell](https://redirect.github.com/jamescassell) made their first contribution in [#&#8203;12807](https://redirect.github.com/jdx/mise/pull/12807)
- [@&#8203;nettlesh](https://redirect.github.com/nettlesh) made their first contribution in [#&#8203;12785](https://redirect.github.com/jdx/mise/pull/12785)
- [@&#8203;Junaid-PK](https://redirect.github.com/Junaid-PK) made their first contribution in [#&#8203;12754](https://redirect.github.com/jdx/mise/pull/12754)
- [@&#8203;kejne](https://redirect.github.com/kejne) made their first contribution in [#&#8203;12731](https://redirect.github.com/jdx/mise/pull/12731)
- [@&#8203;dylanpulver](https://redirect.github.com/dylanpulver) made their first contribution in [#&#8203;12740](https://redirect.github.com/jdx/mise/pull/12740)
- [@&#8203;Svector-anu](https://redirect.github.com/Svector-anu) made their first contribution in [#&#8203;12714](https://redirect.github.com/jdx/mise/pull/12714)
- [@&#8203;vladbisceanu](https://redirect.github.com/vladbisceanu) made their first contribution in [#&#8203;12697](https://redirect.github.com/jdx/mise/pull/12697)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.1...v2026.9.2>

##### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.1`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.1): : Bootstrap firewall limiting, scoped locked mode, and lazy tools in tasks

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.9.0...vfox-v2026.9.1)

This release expands bootstrap system setup with firewall rate limiting and more systemd directives, adds a scoped locked mode for mixing distribution and user tool policies, and fixes lazy tools so they install when invoked from tasks and `mise x`.

#### Added

- **bootstrap:** New `action = "limit"` for incoming TCP firewall rules rate-limits connections per source. It maps to UFW's native limiting and bounded per-source IPv4/IPv6 nftables meters; firewalld fails closed since it cannot express safe per-source limiting. Limit rules are treated as preserving SSH access by the lockout guard. ([#&#8203;12669](https://redirect.github.com/jdx/mise/pull/12669) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Bootstrap systemd units now support `requires` (`Requires=`), repeatable `environment_file` (`EnvironmentFile=`), `nice`, and `umask`, with validation for nice ranges (-20 to 19) and octal umasks. Note that `requires` does not imply ordering; use `after` for that. ([#&#8203;12683](https://redirect.github.com/jdx/mise/pull/12683) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** New global-only `locked_scopes` setting (also `MISE_LOCKED_SCOPES`) lets you scope locked mode to specific config sources, so a distribution can ship rolling or lazy tools through `/etc/mise/config.toml` without forcing users to maintain a system lockfile. Defaults to all three scopes to preserve existing behavior. ([#&#8203;12667](https://redirect.github.com/jdx/mise/pull/12667) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [settings]
  locked_scopes = ["project", "global"]
  ```

#### Fixed

- **task:** Lazy tools (`lazy = true`) now install when their command is invoked from a `mise run` task or `mise x`, matching the behavior of an activated shell. mise inserts the shim farms after real tool paths for lazy toolsets and reconciles missing bootstrap shims for hand-edited declarations, so tasks no longer fail with `command: not found`. ([#&#8203;12687](https://redirect.github.com/jdx/mise/pull/12687) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **env:** Environment variables configured for removal (`false` values and unset directives) are now consistently unset across `mise exec`, tasks, `mise env`, hook-env, deps, and tool stubs, so inherited or secret variables are no longer leaked back into child processes. ([#&#8203;12664](https://redirect.github.com/jdx/mise/pull/12664) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **shim:** `mise reshim` can now safely target shared executable directories like `~/.local/bin` or `/usr/local/bin`. It identifies mise-owned shims and only replaces or prunes those, leaving unmanaged files and symlinks in place, and `--force` rebuilds mise-owned shims rather than wiping the whole directory. ([#&#8203;12675](https://redirect.github.com/jdx/mise/pull/12675) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew-cask:** Cask binaries that reference their surrounding extracted payload now keep that payload beside the staged binary, so tools like `codex` that exec helpers or read manifests from their own tree continue working after temporary install files are cleaned up. ([#&#8203;12686](https://redirect.github.com/jdx/mise/pull/12686) by [@&#8203;azohra](https://redirect.github.com/azohra))
- **upgrade:** Fixed recursive deferred tool pruning that could hang new terminals when prompt hooks triggered a full tracked-config scan. Deferred cleanup now takes a non-blocking lock and is skipped for shell-integration and `--no-config` commands. ([#&#8203;12676](https://redirect.github.com/jdx/mise/pull/12676) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** Install failures caused by a missing marker file now name the affected file and operation instead of reporting a bare OS error. ([#&#8203;12624](https://redirect.github.com/jdx/mise/pull/12624) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **install:** `mise install --dry-run` for an s3 tool that only declares a URL for another platform now reports the missing platform and available options instead of falsely claiming it would install. ([#&#8203;12641](https://redirect.github.com/jdx/mise/pull/12641) by [@&#8203;hktitof](https://redirect.github.com/hktitof))

#### Changed

- **registry:** Registry command (shim) names are now inferred from the preferred Aqua package at build time, removing redundant `bins` declarations from hundreds of registry entries. Explicit `bins` are kept only where mise intentionally exposes a different command set, and `mise registry --json` exposes the inferred names. ([#&#8203;12668](https://redirect.github.com/jdx/mise/pull/12668) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### New Contributors

- [@&#8203;azohra](https://redirect.github.com/azohra) made their first contribution in [#&#8203;12686](https://redirect.github.com/jdx/mise/pull/12686)
- [@&#8203;hktitof](https://redirect.github.com/hktitof) made their first contribution in [#&#8203;12641](https://redirect.github.com/jdx/mise/pull/12641)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.9.0...v2026.9.1>

#### 💚 Sponsor mise

mise is built and maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer at [**entire.io**](https://entire.io/), the title sponsor of his open source work.

If mise saves you or your team time, please consider becoming an [individual or company sponsor](https://jdx.dev/sponsors.html). Your support funds ongoing development and helps keep mise fast, free, and independent.

### [`v2026.9.0`](https://redirect.github.com/jdx/mise/releases/tag/v2026.9.0): : Lazy Tool Shims

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.8.16...vfox-v2026.9.0)

This release introduces lazy tool shims, which install a tool automatically the first time one of its commands is invoked, plus an opt-in Erlang precompiled OS override and a fix for nested `mise` calls during postinstall hooks.

#### Added

- **tools:** Declare a tool as lazy in `[tools]` with `lazy = true` and mise generates bootstrap shims into its normal user/system shim farms. The provider is installed only when one of its commands is first called, then executes immediately; subsequent calls run the real binary without another mise dispatch. Registry tools derive their command names from registry `bins`, while explicit or non-registry backends declare them with `lazy_bins`. A bare `mise install` skips lazy declarations; use `mise install --include-lazy` to provision them all. New global path settings (`shims_dir`/`MISE_SHIMS_DIR`, `system_installs_dir`/`MISE_SYSTEM_INSTALLS_DIR`, `system_shims_dir`/`MISE_SYSTEM_SHIMS_DIR`) and `mise reshim --system` support system-scoped and collocated layouts. ([#&#8203;12594](https://redirect.github.com/jdx/mise/pull/12594) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools]
  node = { version = "24", lazy = true }
  "github:example/acme" = { version = "1.2.3", lazy = true, lazy_bins = ["acme", "acmectl"] }
  ```
- **erlang:** New `erlang.precompiled_os` setting (and `MISE_ERLANG_PRECOMPILED_OS`) opts into a specific Bob Ubuntu build target—including Ubuntu 26.04—on compatible glibc distributions. Automatic target detection is unchanged, and lockfiles now preserve the selected OS for reproducible builds. Note the portability constraints around shared-library SONAMEs documented with the setting. ([#&#8203;12637](https://redirect.github.com/jdx/mise/pull/12637) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **install:** Postinstall hooks now activate the exact tool version being installed, so nested `mise` invocations (such as `mise which` or `mise ls`) resolve that version instead of an older discoverable one—or losing the tool entirely when it was declared through a custom config path with `mise use --path`. ([#&#8203;12635](https://redirect.github.com/jdx/mise/pull/12635) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Registry

- Added `engram` ([#&#8203;12480](https://redirect.github.com/jdx/mise/pull/12480) by [@&#8203;AndryOre](https://redirect.github.com/AndryOre))
- Added `gentle-ai` ([aqua:Gentleman-Programming/gentle-ai](https://redirect.github.com/Gentleman-Programming/gentle-ai)) ([#&#8203;12444](https://redirect.github.com/jdx/mise/pull/12444) by [@&#8203;AndryOre](https://redirect.github.com/AndryOre))

#### New Contributors

- [@&#8203;AndryOre](https://redirect.github.com/AndryOre) made their first contribution in [#&#8203;12444](https://redirect.github.com/jdx/mise/pull/12444)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.8.16...v2026.9.0>

#### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

### [`v2026.8.16`](https://redirect.github.com/jdx/mise/releases/tag/v2026.8.16): : Command wrappers and Windows shell fixes

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.8.15...vfox-v2026.8.16)

This release adds configurable command wrappers for intercepting ordinary commands, alongside a batch of shell and PATH fixes that make Windows activation more reliable and clearer upgrade and error messaging.

#### Added

- **shims:** New `[wrappers]` config lets you intercept an ordinary command name with a different command, arguments, and environment. Wrappers take precedence over mise-managed tools, and mise strips its dispatch directories before delegating so the underlying tool still resolves from mise or the system. Works with normal activation, `activate --shims`, and `mise exec`; managed wrapper shims are refreshed by `mise reshim`. ([#&#8203;12617](https://redirect.github.com/jdx/mise/pull/12617) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools]
  mr-boxington = "1.1.0"

  [wrappers.cargo]
  command = "mbx"
  env = { MBX_CARGO_SHIM_MODE = "1" }
  ```

#### Fixed

- **elvish:** `mise env` and `mise activate --shims` now emit valid Elvish quoting instead of bash's. This fixes several corruptions that mostly surfaced on Windows: stray quotes and the wrong PATH separator in `activate --shims`, apostrophes and `!` arriving with backslashes, and `\n` sequences in paths like `C:\nodejs` being turned into newlines. ([#&#8203;12584](https://redirect.github.com/jdx/mise/pull/12584) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **fish:** `mise env -s fish` now splits PATH on the host's separator and recognizes the `Path` spelling on Windows, so semicolon-separated Windows paths are no longer severed at each drive letter. Empty PATH segments are dropped rather than adding the current directory. ([#&#8203;12582](https://redirect.github.com/jdx/mise/pull/12582) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **http:** The "No URL for platform" error now lists every platform key a tool actually declares in `mise.toml`, including typos and unrecognized names, instead of falling back to a misleading "requires 'url' option" message with an empty available list. ([#&#8203;12580](https://redirect.github.com/jdx/mise/pull/12580) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **upgrade:** When tools are current for their configured version ranges but newer releases exist beyond those ranges, `mise upgrade` no longer prints "All tools are up to date". It now lists the affected tools with their current and available versions plus the config source, and points to `mise upgrade --bump`. ([#&#8203;12613](https://redirect.github.com/jdx/mise/pull/12613) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```text
  Newer versions are available but do not match the configured version ranges:
    node 26.7.0 → 26.8.1 (~/.config/mise/config.toml)
  Run `mise upgrade --bump` to update the configuration and upgrade.
  ```
- **task:** Double-dash task arguments are now preserved with `double_dash="preserve"`. The first `--` separator was previously consumed by the outer CLI parser and lost before reaching the task. ([#&#8203;12628](https://redirect.github.com/jdx/mise/pull/12628) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** Embedded Aube npm installs are now isolated as a standalone one-package workspace, so a `pnpm-workspace.yaml` above `MISE_DATA_DIR` can no longer redirect the install into the outer workspace or mutate it. This mainly affects GitLab CI layouts where the data dir lives under `CI_PROJECT_DIR`. ([#&#8203;12630](https://redirect.github.com/jdx/mise/pull/12630) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Repository status checks now honor the global `--jobs` limit, so `mise bootstrap` status, apply, and update run these read-only git checks with bounded concurrency. ([#&#8203;12629](https://redirect.github.com/jdx/mise/pull/12629) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Changed

- **registry:** `acli`, `mimirtool`, and `specstory` are no longer restricted to Linux and macOS and can now be installed and run on Windows. ([#&#8203;12627](https://redirect.github.com/jdx/mise/pull/12627) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **registry:** `llama.cpp` resolution now includes prereleases, since upstream began flagging its rolling `bNNNN` builds as GitHub prereleases. The existing minimum-release-age and `b` version prefix behavior is unchanged. ([#&#8203;12614](https://redirect.github.com/jdx/mise/pull/12614) by [@&#8203;jdx](https://redirect.github.com/jdx))

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.8.15...v2026.8.16>

#### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42MS4zIiwidXBkYXRlZEluVmVyIjoiNDQuNjkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-09-08 08:16:51 -07:00
renovate[bot] e57cd9e6ca chore(deps): update redis docker digest to 298e5b3 (#688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-31 11:57:17 -07:00
renovate[bot] cab81a25e0 chore(deps): update postgres:18 docker digest to 4ef4dbc (#687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-31 11:56:56 -07:00
renovate[bot] ada9f90a1c chore(deps): update ci workflows (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-31 11:52:40 -07:00
renovate[bot] ee194b7110 chore(deps): update redis docker digest to 691577a (#683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-24 11:59:46 -07:00
renovate[bot] da5f3b08e7 chore(deps): update dependency @tsconfig/bases to v1.0.27 (#684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-24 11:59:26 -07:00
renovate[bot] ddb3e848dc chore(deps): update dependency jdx/mise to v2026.8.11 (#685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-24 11:58:39 -07:00
renovate[bot] b79c67f88c chore(deps): update dependency uuid to v14.0.2 (#686)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-24 11:58:20 -07:00
renovate[bot] fbdd599845 chore(deps): update postgres:18 docker digest to 06cad38 (#679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 09:45:28 -07:00
renovate[bot] fbb161ecd2 chore(deps): update dependency pg to v8.23.0 (#681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 09:45:05 -07:00
renovate[bot] f1b13a9840 chore(deps): update ci workflows (#680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 09:44:57 -07:00
renovate[bot] 3be085cd8f chore(deps): update lint (#682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-17 09:44:05 -07:00
renovate[bot]andWill Schurman b5f6cb6e4e chore(deps): update lint (#678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Will Schurman <[email protected]>
2026-08-10 09:47:36 -07:00
renovate[bot] 2e5f9ce7f5 chore(deps): update postgres:18 docker digest to a02db8c (#675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 09:43:16 -07:00
renovate[bot] 38122c8bd2 chore(deps): update redis docker digest to 344e394 (#676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 09:42:18 -07:00
renovate[bot] 96ead4f360 chore(deps): update dependency jdx/mise to v2026.8.3 (#677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 09:33:24 -07:00
renovate[bot] 7cc580550b chore(deps): update dependency ip-address to v10.2.2 [security] (#674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-04 08:19:24 -07:00
Will Schurman 🦦 a4740446e0 chore: treat typedoc warnings as errors to keep evergreen (#672) 2026-08-03 16:04:07 -07:00
Will Schurman 🦦 d12496dc1b chore: fix typedoc warnings (#671) 2026-08-03 16:04:07 -07:00
Will Schurman 613d5d7424 v0.66.0 2026-08-03 09:28:42 -07:00
renovate[bot] dd93c0144e chore(deps): update redis docker digest to a7efd4e (#667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 09:11:41 -07:00
renovate[bot] 0137c84baf chore(deps): update lint (#669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 09:10:51 -07:00
renovate[bot] f196b0dcfb chore(deps): update ci workflows (#668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 09:02:49 -07:00
renovate[bot] 6ec73542af chore(deps): update node.js to v24.18.1 (#670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 09:02:01 -07:00
Gabe Debes 59e05de4dc feat!: emit a dedicated metric event for count queries (#662)
# Why

Count queries reported their result through the load event, so a `COUNT(*)` returning 8k looked like we loaded 8k entities.

# How

Give counts their own event (`logDataManagerCountEvent`) so the count number stops being read as entities loaded. Breaking: `IEntityMetricsAdapter` implementers need to add the method; I'm happy to go for another path, non-breaking change if you prefer, but this felt more fundamentally correct 

<img width="480" height="240" alt="giphy" src="https://github.com/user-attachments/assets/4630fefc-cca4-42ca-afc4-22b9919df4a8" />

# Test Plan

CI. Updated the metrics unit test to check the counts route to the new event.
2026-07-27 09:01:57 -07:00
renovate[bot] 017642a1b2 chore(deps): update redis docker digest to c88d347 (#663)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| redis | digest | `234c902` → `c88d347` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-27 07:47:44 -07:00
renovate[bot] 44e18b2011 chore(deps): update dependency ctix to v2.8.2 (#664)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [ctix](https://imjuni.github.io/ctix) ([source](https://redirect.github.com/imjuni/ctix)) | [`2.8.1` → `2.8.2`](https://renovatebot.com/diffs/npm/ctix/2.8.1/2.8.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/ctix/2.8.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ctix/2.8.1/2.8.2?slim=true) |

---

### Release Notes

<details>
<summary>imjuni/ctix (ctix)</summary>

### [`v2.8.2`](https://redirect.github.com/imjuni/ctix/releases/tag/v2.8.2)

[Compare Source](https://redirect.github.com/imjuni/ctix/compare/v2.8.1...v2.8.2)

#### What's Changed

- chore(release): prepare v2.8.2 by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;202](https://redirect.github.com/imjuni/ctix/pull/202)

**Full Changelog**: <https://github.com/imjuni/ctix/compare/v2.8.1...v2.8.2>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-27 07:47:33 -07:00
renovate[bot] a2fd6d3f3f chore(deps): update ci workflows (#665)
> ℹ️ **Note**
> 
> This PR body was truncated due to platform limits.

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) | action | minor | `v6.0.3` → `v6.1.0` |
| [actions/setup-node](https://redirect.github.com/actions/setup-node) | action | minor | `v6.4.0` → `v6.5.0` |
| [jdx/mise](https://redirect.github.com/jdx/mise) |  | patch | `v2026.7.5` → `v2026.7.14` |
| [jdx/mise-action](https://redirect.github.com/jdx/mise-action) | action | patch | `v4.2.0` → `v4.2.3` |

---

### Release Notes

<details>
<summary>actions/checkout (actions/checkout)</summary>

### [`v6.1.0`](https://redirect.github.com/actions/checkout/compare/v6.0.3...v6.1.0)

[Compare Source](https://redirect.github.com/actions/checkout/compare/v6.0.3...v6.1.0)

</details>

<details>
<summary>actions/setup-node (actions/setup-node)</summary>

### [`v6.5.0`](https://redirect.github.com/actions/setup-node/releases/tag/v6.5.0)

[Compare Source](https://redirect.github.com/actions/setup-node/compare/v6.4.0...v6.5.0)

##### What's Changed

- Update [@&#8203;actions/cache](https://redirect.github.com/actions/cache) to 5.1.0 and add security overrides for undici and fast-xml-parser by [@&#8203;HarithaVattikuti](https://redirect.github.com/HarithaVattikuti) in [#&#8203;1579](https://redirect.github.com/actions/setup-node/pull/1579)

**Full Changelog**: <https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0>

</details>

<details>
<summary>jdx/mise (jdx/mise)</summary>

### [`v2026.7.14`](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.14): : Multi-asset GitHub installs, safer defaults, and Windows pip

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.7.13...v2026.7.14)

This release adds overlay installs for GitHub-based tools, closes a config-trust security gap around default shell arguments, and lands a wide batch of correctness fixes across tasks, lockfiles, npm, Swift, brew casks, and Windows Python.

##### Added

- **github:** the GitHub/GitLab/Forgejo release backend now accepts `additional_asset_patterns`, so a single install can overlay multiple release archives from the same tag into one installation directory. Each supplemental artifact is locked and verified (checksums and provenance) on its own, and `--locked` fails if the recorded set no longer matches. This models release layouts like Ollama's optional ROCm archive without making a large payload unconditional. ([#&#8203;11272](https://redirect.github.com/jdx/mise/pull/11272) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools."github:ollama/ollama"]
  version = "latest"
  additional_asset_patterns = ["ollama-linux-amd64-rocm.tgz"]
  ```
- **npm:** new `allow_low_downloads` tool option lets an embedded aube install bypass the weekly-download popularity gate for the requested package only, so curated tools like `bibtex-tidy` and `purty` install again without `npm.shell_out`. Transitive dependencies still hit the gate. ([#&#8203;11305](https://redirect.github.com/jdx/mise/pull/11305) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **env:** structured env files (`env._.file` loading JSON/YAML/TOML) support an explicit `expand = true` option for shell-style variable expansion, including references to earlier values. See Fixed below for the default-behavior change. ([#&#8203;11269](https://redirect.github.com/jdx/mise/pull/11269) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Fixed

- **env:** values loaded from JSON/YAML/TOML env files are literal by default again, fixing a regression where every value was shell-expanded when `env_shell_expand` was on. That corrupted literals such as bcrypt-style `$6$salt$hash` and could pull in matching process-environment values. Opt back into expansion with `expand = true`. ([#&#8203;11269](https://redirect.github.com/jdx/mise/pull/11269) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **python:** `pip` is now usable on fresh Windows installs. mise synthesizes `pip.cmd`/`pip3.cmd` wrappers, adds the install's `Scripts` directory to PATH so pip-installed console scripts resolve, and fixes default-package installation on Windows. ([#&#8203;11278](https://redirect.github.com/jdx/mise/pull/11278) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **github:** `.exe` release assets are now preferred on Windows, so tools that ship both an extensionless and `.exe` binary no longer install the non-runnable one and fail with "cannot find binary path". ([#&#8203;11257](https://redirect.github.com/jdx/mise/pull/11257) by [@&#8203;gologames](https://redirect.github.com/gologames))
- **github:** a rejected GitHub token (401) now produces an actionable error that names the token source (gh CLI, `github_tokens.toml`, OAuth, env var, etc.) and includes GitHub's response and a remediation hint, instead of a bare `401 Unauthorized`. ([#&#8203;11236](https://redirect.github.com/jdx/mise/pull/11236) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **backend:** an archive containing a single binary with an OS/arch suffix (e.g. `gdscript-formatter-macos-aarch64`) is now renamed to its clean name on PATH, matching the existing behavior for single-file downloads. ([#&#8203;11232](https://redirect.github.com/jdx/mise/pull/11232) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **brew:** brew-cask installs handle more real-world casks — completion stanzas and `system_command` in lifecycle blocks are supported, and artifact links into root-owned directories like `/usr/local/bin` now elevate through mise's sudo policy instead of failing with permission errors. Fixes installing `docker-desktop`. ([#&#8203;11273](https://redirect.github.com/jdx/mise/pull/11273) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **lockfile:** `prefix:` tool requests now honor the locked version on `mise install` instead of silently re-resolving to the newest match. Constrained upgrades via `mise upgrade`/`mise lock --bump` still work. ([#&#8203;11255](https://redirect.github.com/jdx/mise/pull/11255) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **aqua:** cross-platform lockfiles for aqua HTTP packages preserve a reachable `v`-prefixed URL, fixing 404s where an entry locked to another platform's unprefixed URL. ([#&#8203;11267](https://redirect.github.com/jdx/mise/pull/11267) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **swift:** Swift lockfile entries now record the target Linux distro (`ubuntu24.04`, `fedora39`, `ubi9`, ...) so checksums are no longer verified across mismatched distro tarballs, and `mise lock` writes meaningful entries and re-locks correctly after changing `swift.platform`. ([#&#8203;11299](https://redirect.github.com/jdx/mise/pull/11299) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **task:** with a git worktree checked out inside the main checkout, mise no longer loads the enclosing monorepo root's tasks into the nested root's namespace. Env, tools, and vars still inherit as before. ([#&#8203;11283](https://redirect.github.com/jdx/mise/pull/11283) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **task:** changing a task's `run` command, `sources`, or `outputs` now invalidates its cached state, so tasks are no longer incorrectly skipped after such edits. ([#&#8203;11288](https://redirect.github.com/jdx/mise/pull/11288) by [@&#8203;rabadin](https://redirect.github.com/rabadin))
- **task:** a task's source hash is now persisted only after a successful run, so a failed run no longer marks stale sources as up to date. ([#&#8203;11296](https://redirect.github.com/jdx/mise/pull/11296) by [@&#8203;rabadin](https://redirect.github.com/rabadin))
- **completions:** shell completion no longer offers mise's own global flags after a task name (where they aren't valid), and a task flag that shares a name with a mise flag now completes its own values correctly. ([#&#8203;11284](https://redirect.github.com/jdx/mise/pull/11284) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** npm package versions with very large numeric components (up to `Number.MAX_SAFE_INTEGER`) now sort correctly, fixing cases where a `0.0.*` build could be picked over a newer stable release. ([#&#8203;11280](https://redirect.github.com/jdx/mise/pull/11280) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** aube trust-downgrade failures now surface a clearer explanation, and aube's own progress display no longer competes with mise's output (bumped to 1.33.1). ([#&#8203;11292](https://redirect.github.com/jdx/mise/pull/11292), [#&#8203;11308](https://redirect.github.com/jdx/mise/pull/11308) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** aube `allow_builds` is now serialized as a map. ([#&#8203;11262](https://redirect.github.com/jdx/mise/pull/11262) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **version:** the update-check cache now negative-caches, so machines that can't reach the network (or run a build newer than the latest release) stop re-running the full check — including building an HTTP client and parsing the CA trust store — on every invocation. This is a significant speedup for commands like `mise --version` in those cases. ([#&#8203;11285](https://redirect.github.com/jdx/mise/pull/11285) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **env:** remaining `std::env::vars()` call sites now use `vars_safe()`. ([#&#8203;11309](https://redirect.github.com/jdx/mise/pull/11309) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

##### Changed

- **usage:** every mise command now declares its effect on the system — read-only, modifies state, or destructive — surfaced in the command reference. This requires usage 4.0 (`min_usage_version` bumped), so shell completions and doc rendering now depend on it. ([#&#8203;11306](https://redirect.github.com/jdx/mise/pull/11306) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Security

- **config:** the Unix/Windows default file and inline shell-argument settings are now global-only. Because local config is loaded before trust evaluation, an untrusted repository could previously influence how commands from trusted sources were executed. Global config and `MISE_*` environment variables still apply. Reported by [@&#8203;arpitjain099](https://redirect.github.com/arpitjain099). ([#&#8203;11293](https://redirect.github.com/jdx/mise/pull/11293) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Performance

- **cli:** the clap command tree is no longer rebuilt twice on every invocation. ([#&#8203;11297](https://redirect.github.com/jdx/mise/pull/11297) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Documentation

- **npm:** documented Socket integrations. ([#&#8203;11268](https://redirect.github.com/jdx/mise/pull/11268) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** corrected the `bin_path`/`asset_pattern` template variable docs. ([#&#8203;11298](https://redirect.github.com/jdx/mise/pull/11298) by [@&#8203;jdx](https://redirect.github.com/jdx))
- Retargeted a dead clap.rs link to docs.rs/clap. ([#&#8203;11194](https://redirect.github.com/jdx/mise/pull/11194) by [@&#8203;Bartok9](https://redirect.github.com/Bartok9))
- Added a generated `llms.txt` index for AI agents. ([#&#8203;11300](https://redirect.github.com/jdx/mise/pull/11300) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Registry

- Added `checkstyle` ([#&#8203;11287](https://redirect.github.com/jdx/mise/pull/11287) by [@&#8203;zeitlinger](https://redirect.github.com/zeitlinger)), `grok` ([#&#8203;11291](https://redirect.github.com/jdx/mise/pull/11291)), and `tak` ([#&#8203;11307](https://redirect.github.com/jdx/mise/pull/11307)) by [@&#8203;jdx](https://redirect.github.com/jdx).

##### New Contributors

- [@&#8203;gologames](https://redirect.github.com/gologames) made their first contribution in [#&#8203;11257](https://redirect.github.com/jdx/mise/pull/11257)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.7.13...v2026.7.14>

##### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

### [`v2026.7.13`](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.13): : Multi-binary renaming, cask completions, and go.mod support

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.7.12...vfox-v2026.7.13)

This release expands archive backends to rename multiple binaries from a single release, adds Homebrew cask shell completions and `go.mod` version-file support, and fixes a broad batch of task, install, lockfile, and language-plugin correctness issues.

#### Added

- **backend:** `rename_exe` now accepts a table mapping source patterns to target names, so an archive that ships several executables can expose all of them cleanly on PATH instead of only one. The existing string form is unchanged. ([#&#8203;11231](https://redirect.github.com/jdx/mise/pull/11231) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  [tools."github:DanielGavin/ols"]
  version = "latest"
  rename_exe = { "ols-*" = "ols", "odinfmt-*" = "odinfmt" }
  ```
- **brew:** brew-cask now installs shell completions (declared `bash_completion`/`fish_completion`/`zsh_completion` files and `generate_completions_from_executable` for bash, zsh, fish, and pwsh) instead of skipping them, tracking them in cask receipts and installed-state checks. ([#&#8203;11198](https://redirect.github.com/jdx/mise/pull/11198) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **go:** `go.mod` can now be used as an idiomatic version file. A `toolchain goX.Y.Z` directive resolves as an exact pin, while a `go X.Y` minimum resolves to the latest matching patch. It is opt-in and unchanged unless enabled per-tool. ([#&#8203;11213](https://redirect.github.com/jdx/mise/pull/11213) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

  ```sh
  mise settings add idiomatic_version_file_enable_tools go
  ```
- **task:** PowerShell task shells (`pwsh`/`powershell`) now run with `-NoProfile` by default, matching how mise spawns POSIX shells, so a profile that mutates `PATH` (such as a mise activation snippet) can no longer shadow a task's own tools and cause confusing "cannot find binary path" errors. Opt out with the new `windows_powershell_no_profile` setting (`MISE_WINDOWS_POWERSHELL_NO_PROFILE=false`). ([#&#8203;11199](https://redirect.github.com/jdx/mise/pull/11199) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **env:** `_.path`/`_.file`/`_.source` directives within a single `[env]._` block are now resolved in written order, so a `_.path` can reference a variable exported by a `_.source` written before it. ([#&#8203;11163](https://redirect.github.com/jdx/mise/pull/11163) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **install:** failed installs no longer print a contradictory "installed but not activated" hint before the real error. ([#&#8203;11227](https://redirect.github.com/jdx/mise/pull/11227) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** logical state mutations during install are now serialized to avoid races. ([#&#8203;11207](https://redirect.github.com/jdx/mise/pull/11207) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **lockfile:** upgrading a locked `github:` tool to a newer version that is already installed on disk no longer triggers a false supply-chain "provenance regression" error. ([#&#8203;11230](https://redirect.github.com/jdx/mise/pull/11230) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **npm:** aube install failures now surface the full cause chain instead of an opaque "failed to resolve dependencies" message, with mise-native remediation guidance (`trust_policy_excludes` or `npm.shell_out`) for trust-downgrade errors. ([#&#8203;11226](https://redirect.github.com/jdx/mise/pull/11226) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **task:** inline task definitions now follow a consistent first-wins precedence model across local, `conf.d`, and monorepo configs, so lower-precedence metadata no longer leaks into script tasks and the winning config's context is preserved. ([#&#8203;11103](https://redirect.github.com/jdx/mise/pull/11103) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **task:** source freshness now tracks files correctly for workspace-rooted `sources` patterns used in nested subproject tasks, so edits inside a subproject trigger a rerun. ([#&#8203;11202](https://redirect.github.com/jdx/mise/pull/11202) by [@&#8203;rabadin](https://redirect.github.com/rabadin))
- **brew:** cask upgrades handle structured `preflight_steps`/`postflight_steps` for move/remove operations, and cleanup-only `zap` `pkgutil` IDs are no longer treated as install receipts (which could leave pkg cask status permanently missing). ([#&#8203;11197](https://redirect.github.com/jdx/mise/pull/11197) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew:** cask upgrades no longer fail when removing a protected app backup (e.g. docker-desktop) hits "Permission denied", recovering permissions the way Homebrew does before retrying. ([#&#8203;11219](https://redirect.github.com/jdx/mise/pull/11219) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **python:** a `python3` executable is now provided on Windows. ([#&#8203;11212](https://redirect.github.com/jdx/mise/pull/11212) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **ruby:** custom `ruby.precompiled_url = "owner/repo"` sources now fetch release metadata directly from GitHub instead of the restricted versions host, avoiding 403 warnings. ([#&#8203;11154](https://redirect.github.com/jdx/mise/pull/11154) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **ruby:** Gemfile version pins with multi-digit segments (e.g. `ruby "3.4.10"`) are now parsed correctly. ([#&#8203;11229](https://redirect.github.com/jdx/mise/pull/11229) by [@&#8203;capnregex](https://redirect.github.com/capnregex))
- **bootstrap:** `./`-prefixed relative `[bootstrap.repos]` paths no longer display with a leading `./` component. ([#&#8203;11214](https://redirect.github.com/jdx/mise/pull/11214) by [@&#8203;lilienblum](https://redirect.github.com/lilienblum))
- **http:** invalid URLs now return an error instead of panicking. ([#&#8203;11160](https://redirect.github.com/jdx/mise/pull/11160) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **settings:** avoid a panic when a parent settings key is an inline table. ([#&#8203;11184](https://redirect.github.com/jdx/mise/pull/11184) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **self-update:** the updater's TLS backend now matches the feature it was built with, fixing failures when compiled with `native-tls`. ([#&#8203;10834](https://redirect.github.com/jdx/mise/pull/10834) by [@&#8203;bltavares](https://redirect.github.com/bltavares))

#### Deprecated

- **python:** the legacy `virtualenv` tool option is deprecated in favor of the `_.python.venv` env directive. It now emits a warning and is scheduled for removal around `2027.7.0`. ([#&#8203;11234](https://redirect.github.com/jdx/mise/pull/11234) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

#### Documentation

- **python:** clarify that `uv_venv_auto` requires a `uv.lock` file. ([#&#8203;11223](https://redirect.github.com/jdx/mise/pull/11223) by [@&#8203;jdx](https://redirect.github.com/jdx))
- Fix dead links in the contributing guide and the aqua-registry/pipx installation docs. ([#&#8203;11123](https://redirect.github.com/jdx/mise/pull/11123), [#&#8203;11167](https://redirect.github.com/jdx/mise/pull/11167) by [@&#8203;Bartok9](https://redirect.github.com/Bartok9))

#### New Contributors

- [@&#8203;capnregex](https://redirect.github.com/capnregex) made their first contribution in [#&#8203;11229](https://redirect.github.com/jdx/mise/pull/11229)
- [@&#8203;rabadin](https://redirect.github.com/rabadin) made their first contribution in [#&#8203;11202](https://redirect.github.com/jdx/mise/pull/11202)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.7.12...v2026.7.13>

#### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

### [`v2026.7.12`](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.12): : Safe mode, node-free npm, and a built-in OCI push client

[Compare Source](https://redirect.github.com/jdx/mise/compare/vfox-v2026.7.11...vfox-v2026.7.12)

This release centers on running mise safely against untrusted config, shipping node-free `npm:` version resolution and installs, and replacing external tools (skopeo/crane, gpg) with built-in, dependency-free implementations. Along the way it fixes a `prefer_offline` timeout regression, several config/env correctness issues, and platform-specific problems on Windows and macOS.

#### Highlights

- New `MISE_SAFE=1` mode turns mise into an inert config reader so automation (CI, Renovate) can run `mise lock --bump` against untrusted branches without trust prompts or arbitrary code execution.
- The `npm:` backend no longer needs node or npm installed to resolve or install packages — version metadata is fetched over HTTP and installs run in-process via the embedded aube package manager.
- `mise oci push` gained a full built-in registry client: no more skopeo/crane, with blob dedup, chunked uploads, retries, cross-repo mounts, layer reuse from prior images, and multi-arch image indexes.

#### Added

- **Safe mode:** a new global-only `safe` setting (`MISE_SAFE=1`) makes mise refuse or ignore anything that would let project config execute code or inject environment. Blocked (with errors): template `exec()`/`read_file()`, `_.source`, hooks, tasks, asdf plugin scripts, and plugin installs. Ignored: project `[env]`, `_.path`, `_.file`, `[shell_alias]`, and `[settings]`. HTTP-based version resolution, lockfile refresh/bump, and already-installed/embedded plugins keep working. Because a config loaded this way is inert, safe mode also skips the trust requirement. ([#&#8203;11146](https://redirect.github.com/jdx/mise/pull/11146), [#&#8203;11151](https://redirect.github.com/jdx/mise/pull/11151) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```sh
  MISE_SAFE=1 mise lock --bump --dry-run --json   # detect updates with zero code execution, no trust needed
  ```
- **`mise lock --bump`:** advance fuzzy lockfile selectors (`latest`, `lts`, prefixes like `"20"`) to the newest matching remote versions without installing anything and without touching `mise.toml`. Exact pins stay put. Pairs with `--json` and `--dry-run` for a CI job that opens lockfile-only PRs. ([#&#8203;11145](https://redirect.github.com/jdx/mise/pull/11145) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```console
  $ mise lock --bump --dry-run --json
  [{ "name": "node", "backend": "core:node", "old_versions": ["22.14.0"], "new_versions": ["22.15.0"] }]
  ```
- **Node-free `npm:` backend:** `mise ls-remote npm:*` and `latest` resolution now query the npm registry directly over HTTP, and installs run in-process through the embedded aube package manager. node is only needed to *run* installed tools (and their lifecycle scripts), not to install them. User `~/.npmrc`, scoped registries, auth, and `minimum_release_age` are honored. A new `npm.shell_out` setting (`MISE_NPM_SHELL_OUT`) routes both metadata and installs through the npm CLI for setups relying on npm-only config like `cafile` or client certs. ([#&#8203;11147](https://redirect.github.com/jdx/mise/pull/11147), [#&#8203;11149](https://redirect.github.com/jdx/mise/pull/11149) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **Built-in OCI registry client:** `mise oci push` no longer requires skopeo or crane. It uses an in-tree OCI Distribution client that uploads only blobs the registry is missing, resolves credentials the way docker/podman do (`docker login`/`podman login` is all the setup needed), and answers registry auth challenges so private base-image pulls via `--from` also work. `mise oci run --engine docker` now streams the image into `docker load` instead of needing skopeo. The `--tool` flag was removed. ([#&#8203;11132](https://redirect.github.com/jdx/mise/pull/11132) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **OCI push robustness:** large layers upload in 64 MiB chunks, transient failures retry with mise's standard backoff, base layers are mounted cross-repo (`?mount=`) when they already live on the destination registry (zero bytes transferred), and pushes/pulls now show byte progress. A new `oci.insecure_registries` setting opts non-loopback registries into plain HTTP. ([#&#8203;11141](https://redirect.github.com/jdx/mise/pull/11141) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **OCI layer reuse:** `mise oci push` reuses unchanged tool layers from the previously pushed image (or `--cache-from REF`), skipping the tar/gzip build entirely — reused tools don't even need to be installed locally. `--no-cache` forces a full rebuild. ([#&#8203;11142](https://redirect.github.com/jdx/mise/pull/11142) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **Multi-arch OCI images:** `mise oci push --update-index` points a tag at an OCI image index and upserts the current platform while preserving entries pushed by other architectures, so one runner per arch can assemble a multi-arch tag. ([#&#8203;11144](https://redirect.github.com/jdx/mise/pull/11144) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **Built-in signature verification:** Node.js and Swift download signatures are now verified in-process with the pure-Rust rPGP crate against the bundled keyrings, dropping the external `gpg` binary dependency. Note: previously, if `gpg` was missing and the setting was unset, verification was silently skipped; now verification always runs when enabled — opt out with `gpg_verify = false`. ([#&#8203;11148](https://redirect.github.com/jdx/mise/pull/11148) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **Relative bootstrap repo paths:** `[bootstrap.repos]` destination keys can now be relative paths, resolved against the declaring project's root (with guards preventing escape outside the project tree), making a project's `mise.toml` portable across machines. ([#&#8203;11155](https://redirect.github.com/jdx/mise/pull/11155) by [@&#8203;lilienblum](https://redirect.github.com/lilienblum))

#### Fixed

- **http:** `mise lock`, `ls-remote`, `outdated`, and `upgrade` no longer get capped to a single 3s no-retry attempt when `prefer_offline` is set — they honor the configured `fetch_remote_versions_timeout` and retry budget again, fixing spurious timeouts. Interactive fast paths keep their bounded behavior. ([#&#8203;11190](https://redirect.github.com/jdx/mise/pull/11190) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **lock:** unfiltered `mise lock` no longer prunes OS-restricted tool entries (e.g. `os = ["macos"]`) when run on a platform that doesn't target them, keeping shared lockfiles stable across platforms. ([#&#8203;11175](https://redirect.github.com/jdx/mise/pull/11175) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **env:** values loaded from `_.file` env files now resolve references to variables defined in earlier files or `[env]` blocks instead of collapsing to empty, fixing flaky dotenv behavior. ([#&#8203;11158](https://redirect.github.com/jdx/mise/pull/11158) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **env:** `[env] _.source` now works on Windows by resolving a real POSIX bash (Git Bash / MSYS2, honoring `MISE_BASH_PATH`) instead of the WSL launcher or a missing `bash.exe`, and correctly filters MSYS runtime noise and converts prepended PATH entries back to Windows form. ([#&#8203;11097](https://redirect.github.com/jdx/mise/pull/11097) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **config:** `trusted_config_paths` (`MISE_TRUSTED_CONFIG_PATHS`) now overrides configs previously added to the ignore list, so a settings-trusted config is actually discovered and loaded. ([#&#8203;11152](https://redirect.github.com/jdx/mise/pull/11152) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))
- **config:** `env`/`vars` array parsing is now consistent — accidental array forms for `vars` and task `env`/`vars` are rejected with clear errors, while the intentional `[[env]]` and directive-level arrays (`_.source`, `_.file`, `_.path`) remain supported. ([#&#8203;11060](https://redirect.github.com/jdx/mise/pull/11060) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **config:** `mise config set` no longer panics when descending into a non-table value. ([#&#8203;11153](https://redirect.github.com/jdx/mise/pull/11153) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **config:** clearer warnings for unsupported semver ranges. ([#&#8203;11176](https://redirect.github.com/jdx/mise/pull/11176) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **hook-env:** entering a directory with an untrusted config now prints a single concise warning (`... is not trusted, run 'mise trust' to enable it`) instead of the full error chain. Explicit commands still show the full trust error and prompt. ([#&#8203;11159](https://redirect.github.com/jdx/mise/pull/11159) by [@&#8203;lilienblum](https://redirect.github.com/lilienblum))
- **activate:** `mise activate zsh` no longer hangs non-interactive login shells under Rosetta — a v2026.7.11 regression where env-state snapshotting autoloaded the `zsh/parameter` module via `dlopen`. ([#&#8203;11188](https://redirect.github.com/jdx/mise/pull/11188) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **brew-cask:** case-insensitive app bundle lookup (fixing casks like `yaak`) and correct resolution of preflight-generated wrapper scripts on the extract stage (fixing `vlc`). ([#&#8203;11164](https://redirect.github.com/jdx/mise/pull/11164) by [@&#8203;donbeave](https://redirect.github.com/donbeave))
- **brew:** cask binaries can symlink into `/usr/local` on arm64. ([#&#8203;11174](https://redirect.github.com/jdx/mise/pull/11174) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** qualified prerelease suffixes are now detected correctly. ([#&#8203;11179](https://redirect.github.com/jdx/mise/pull/11179) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **completions:** avoid a network lookup during usage completion. ([#&#8203;11169](https://redirect.github.com/jdx/mise/pull/11169) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **link:** clear a stale incomplete marker when linking a tool. ([#&#8203;11150](https://redirect.github.com/jdx/mise/pull/11150) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **shim:** unresolved Windows exe-shim dispatch (e.g. a project-scoped tool run outside the project) now shows the actionable "no version is set" guidance instead of an opaque "cannot find binary path". ([#&#8203;11189](https://redirect.github.com/jdx/mise/pull/11189) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **vfox:** preserve inner quotes when a vfox Lua command dispatches through `cmd.exe` on Windows, fixing commands such as the Yarn 2+ install flow. ([#&#8203;11166](https://redirect.github.com/jdx/mise/pull/11166) by [@&#8203;finalchild](https://redirect.github.com/finalchild))
- **github/gitlab/forgejo:** statically-linked `linux-musl` assets are now considered a fallback for Android/Termux when a package publishes no Android-specific asset, so tools like starship, bat, and hk can install there. ([#&#8203;10730](https://redirect.github.com/jdx/mise/pull/10730) by [@&#8203;bltavares](https://redirect.github.com/bltavares))
- **deps:** avoid installing tools during dry runs, and enforce the experimental gate for task providers. ([#&#8203;11016](https://redirect.github.com/jdx/mise/pull/11016), [#&#8203;11177](https://redirect.github.com/jdx/mise/pull/11177) by [@&#8203;risu729](https://redirect.github.com/risu729))

#### Documentation

- Document environment-variable backing for file-task arguments and flags, including CLI/env/default precedence. ([#&#8203;11165](https://redirect.github.com/jdx/mise/pull/11165) by [@&#8203;zeitlinger](https://redirect.github.com/zeitlinger))

#### Breaking Changes

- `mise oci push --tool` has been removed. Use `mise oci build -o ./img` followed by `skopeo copy` if you need the old escape hatch. ([#&#8203;11132](https://redirect.github.com/jdx/mise/pull/11132))
- Node/Swift signature verification now always runs when enabled (external `gpg` is no longer required). If you relied on a missing `gpg` binary to skip verification, set `gpg_verify = false` explicitly. ([#&#8203;11148](https://redirect.github.com/jdx/mise/pull/11148))
- The unreleased `npm.use_npm_view` setting is replaced by `npm.shell_out` (`MISE_NPM_SHELL_OUT`). ([#&#8203;11149](https://redirect.github.com/jdx/mise/pull/11149))

#### New Contributors

- [@&#8203;donbeave](https://redirect.github.com/donbeave) made their first contribution in [#&#8203;11164](https://redirect.github.com/jdx/mise/pull/11164)
- [@&#8203;lilienblum](https://redirect.github.com/lilienblum) made their first contribution in [#&#8203;11159](https://redirect.github.com/jdx/mise/pull/11159)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.7.11...v2026.7.12>

#### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

### [`v2026.7.11`](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.11): : Task Source Tracking and Activation Speedups

[Compare Source](https://redirect.github.com/jdx/mise/compare/v2026.7.7...vfox-v2026.7.11)

This release sharpens task handling around remote files, global config, and tool selectors, trims redundant work from shell activation, and fixes several bootstrap and platform-specific edge cases.

> \[!NOTE]
> This is the first published release since v2026.7.7 — versions 2026.7.8 through 2026.7.10 were tagged but never published due to a release pipeline issue. Their changes are included here; see [**Also in this release**](#also-in-this-release-v202678v2026710) below.

#### Added

- **config:** structured tool definitions now accept `version`, `path`, `prefix`, and `ref` selectors consistently in root `[tools]`, inline task definitions, task templates, and file-task headers. Exactly one selector is required, and conflicting, missing, or non-string selectors now fail with a clear configuration error instead of silently defaulting or panicking. ([#&#8203;11069](https://redirect.github.com/jdx/mise/pull/11069) by [@&#8203;risu729](https://redirect.github.com/risu729))

  ```toml
  node = { version = "20" }
  go = { prefix = "1.22" }
  python = { ref = "main" }
  shellcheck = { path = "/opt/shellcheck" }
  ```

#### Fixed

- **task:** remote HTTP and Git-backed task files now have their `#MISE` headers parsed, so metadata like `tools`, `description`, and `hide` and inline TOML overrides are honored just like local file tasks. Git-backed task files are also made executable after cloning and on cache hits. ([#&#8203;11111](https://redirect.github.com/jdx/mise/pull/11111) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **task:** file tasks loaded from user-global or system configs are now correctly treated as global regardless of their include path, so custom global scripts appear under `mise tasks --global`, stay out of `--local`, and use the invoking project root. ([#&#8203;11106](https://redirect.github.com/jdx/mise/pull/11106) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **task:** `mise tasks info`, extended listings, task help, and the MCP tasks resource now report every configuration source contributing to a task, with a new `config_sources` array in JSON output. Changing metadata in a TOML overlay now correctly invalidates the merged file task's cache. ([#&#8203;11098](https://redirect.github.com/jdx/mise/pull/11098) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **watch:** `mise --env <profile> watch` now propagates the selected environment to the watched task, so profile-specific tasks resolve correctly on each rerun. ([#&#8203;11114](https://redirect.github.com/jdx/mise/pull/11114) by [@&#8203;Marukome0743](https://redirect.github.com/Marukome0743))
- **http:** raw executables (such as PHP PHARs like Composer) installed via the HTTP backend are now created readable as well as executable (mode `0755` rather than `0711`), fixing "Could not open input file" errors for interpreters run by non-owner users. ([#&#8203;11135](https://redirect.github.com/jdx/mise/pull/11135) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **java:** `mise latest java@<vendor>` now prefers the base vendor variant over dashed specializations and sorts multi-feature vendor prefixes correctly. ([#&#8203;11109](https://redirect.github.com/jdx/mise/pull/11109) by [@&#8203;roele](https://redirect.github.com/roele))
- **config:** reject conflicting tool selectors at parse time (see Added). ([#&#8203;11069](https://redirect.github.com/jdx/mise/pull/11069) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **bootstrap:** systemd timers can now reference a managed service by its bare TOML key (e.g. `unit = "dotfiles-maintain"`), which resolves to `dev.mise.<name>.service`. Fully qualified names like `nginx.service` are still written verbatim. ([#&#8203;11128](https://redirect.github.com/jdx/mise/pull/11128) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** Homebrew casks that declare `auto_updates` are now accepted instead of failing with an unsupported-lifecycle error. ([#&#8203;11107](https://redirect.github.com/jdx/mise/pull/11107) by [@&#8203;casparbreloh](https://redirect.github.com/casparbreloh))
- **bootstrap:** macOS `defaults` handling now treats a missing key or domain as unset rather than erroring during initial setup. ([#&#8203;11118](https://redirect.github.com/jdx/mise/pull/11118) by [@&#8203;roele](https://redirect.github.com/roele))
- **windows:** `mise uninstall` now removes dangling runtime version pointer files (like `16` or `latest`) and cleans up the now-empty tool directory. ([#&#8203;11095](https://redirect.github.com/jdx/mise/pull/11095) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562))

#### Performance

- **activate:** the redundant initial prompt hook that ran immediately after activation is now skipped for bash, zsh, and fish when the session is unchanged, avoiding an extra `mise` process on shell startup. ([#&#8203;11130](https://redirect.github.com/jdx/mise/pull/11130), [#&#8203;11131](https://redirect.github.com/jdx/mise/pull/11131), [#&#8203;11134](https://redirect.github.com/jdx/mise/pull/11134) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** archive extraction and macOS `hdiutil`/`pkgutil` waits now run via `block_in_place`, so parallel installs no longer starve download progress and other tasks of runtime threads. ([#&#8203;11136](https://redirect.github.com/jdx/mise/pull/11136) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Registry

- Allow the reviewed `@nubjs/nub` postinstall lifecycle script to run via the npm backend so upgrades keep the `~/.nub/shims` hardlinks refreshed; default-deny behavior is preserved for all other packages. ([#&#8203;11126](https://redirect.github.com/jdx/mise/pull/11126) by [@&#8203;risu729](https://redirect.github.com/risu729))

#### Documentation

- **environments:** clarify mise directory variables ([#&#8203;11127](https://redirect.github.com/jdx/mise/pull/11127) by [@&#8203;jdx](https://redirect.github.com/jdx))
- Fix dead VersionFox and MCP documentation links ([#&#8203;11117](https://redirect.github.com/jdx/mise/pull/11117) by [@&#8203;Bartok9](https://redirect.github.com/Bartok9))
- Add an example showing how to alias multiple tools from one GitHub release ([#&#8203;11110](https://redirect.github.com/jdx/mise/pull/11110) by [@&#8203;LukasKnuth](https://redirect.github.com/LukasKnuth))

***

#### Also in this release: v2026.7.8–v2026.7.10

Due to a release pipeline issue, versions 2026.7.8 through 2026.7.10 were tagged but never published. This is the first release since [v2026.7.7](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.7), so it also ships everything below.

##### Added

- **bootstrap:** package manager plugins — vfox Lua plugins can now act as system package managers, with a declarative `[bootstrap.plugins]` config and `mise bootstrap plugins apply|status` commands. Declared plugins install before built-in packages, then plugin-managed packages apply once host tools are available. ([#&#8203;11023](https://redirect.github.com/jdx/mise/pull/11023), [#&#8203;11024](https://redirect.github.com/jdx/mise/pull/11024) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **bootstrap:** new `mise bootstrap repos update` and `mise bootstrap repos exec` commands. Unpinned `[bootstrap.repos]` entries are intentionally never pulled by declarative apply; `repos update` gives an explicit fetch + fast-forward path (with dry-run and path filtering), and `repos exec` runs a command across configured checkouts. `mise bootstrap --update` now also updates repos. ([#&#8203;11022](https://redirect.github.com/jdx/mise/pull/11022) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **registry:** opt-in floating registries — with `registry_floating`, mise fetches the latest released registry metadata (and the current aqua registry) instead of the release-pinned copies. Useful on distributions whose mise package lags behind releases while registry entries keep changing. ([#&#8203;10971](https://redirect.github.com/jdx/mise/pull/10971) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** exact-version fast path — when a request is an exact semver version, the cargo, npm, go, gem, pipx, and dotnet backends now skip fetching the remote version list entirely and let the underlying installer validate it. This speeds up exact pins without a lockfile, first-time `mise install [email protected]`, and `mise x [email protected]`. ([#&#8203;11013](https://redirect.github.com/jdx/mise/pull/11013) by [@&#8203;Turbo87](https://redirect.github.com/Turbo87), [#&#8203;11070](https://redirect.github.com/jdx/mise/pull/11070) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **task:** dependencies whose templated names render to an empty string are now skipped, so templates can conditionally disable a dependency. Empty templated dependency *args* are omitted as well. ([#&#8203;11057](https://redirect.github.com/jdx/mise/pull/11057) by [@&#8203;risu729](https://redirect.github.com/risu729), [#&#8203;11055](https://redirect.github.com/jdx/mise/pull/11055) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Fixed

- **http:** mise now fails fast when the network is unavailable: DNS resolver failures are treated as non-retryable, a process-local circuit opens after hard DNS/connection failures, and remote-version lookups are capped at 3 seconds under `prefer_offline`. Shims, shell activation, and `mise x` make one bounded attempt instead of grinding through full retry schedules per endpoint. ([#&#8203;11066](https://redirect.github.com/jdx/mise/pull/11066) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **config:** stricter, clearer configuration validation: `env` directive value aliases ([#&#8203;11062](https://redirect.github.com/jdx/mise/pull/11062)), `env.mise` directives ([#&#8203;11053](https://redirect.github.com/jdx/mise/pull/11053)), and the experimental monorepo root key ([#&#8203;11052](https://redirect.github.com/jdx/mise/pull/11052)) are deprecated, and non-string `postinstall` hooks are rejected at parse time ([#&#8203;11061](https://redirect.github.com/jdx/mise/pull/11061)) (all by [@&#8203;risu729](https://redirect.github.com/risu729))
- **hooks:** hook definitions are validated more strictly — unknown table fields ([#&#8203;11047](https://redirect.github.com/jdx/mise/pull/11047)) and nested hook arrays ([#&#8203;11051](https://redirect.github.com/jdx/mise/pull/11051)) are rejected, and the spawned-scripts form is deprecated ([#&#8203;11043](https://redirect.github.com/jdx/mise/pull/11043)) (all by [@&#8203;risu729](https://redirect.github.com/risu729))
- **schema:** a large batch of JSON schema corrections so editors validate real configs correctly: OCI copy entries ([#&#8203;11009](https://redirect.github.com/jdx/mise/pull/11009)), bootstrap shell activation ([#&#8203;11004](https://redirect.github.com/jdx/mise/pull/11004)) and bootstrap hooks ([#&#8203;11039](https://redirect.github.com/jdx/mise/pull/11039)), structured task tool options ([#&#8203;11021](https://redirect.github.com/jdx/mise/pull/11021)), required watch file patterns ([#&#8203;11040](https://redirect.github.com/jdx/mise/pull/11040)), plugin manifest fields ([#&#8203;11035](https://redirect.github.com/jdx/mise/pull/11035)), root hook definitions ([#&#8203;11041](https://redirect.github.com/jdx/mise/pull/11041)), integer types for integer settings ([#&#8203;11037](https://redirect.github.com/jdx/mise/pull/11037)), array-typed deps provider paths ([#&#8203;11064](https://redirect.github.com/jdx/mise/pull/11064)) (all by [@&#8203;risu729](https://redirect.github.com/risu729)), and systemd timer/lifecycle options ([#&#8203;11050](https://redirect.github.com/jdx/mise/pull/11050) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **dry runs** no longer cause side effects: task setup is skipped ([#&#8203;11015](https://redirect.github.com/jdx/mise/pull/11015)), install hooks are previewed instead of executed ([#&#8203;11010](https://redirect.github.com/jdx/mise/pull/11010)), and `mise prune --dry-run` preserves runtime symlinks ([#&#8203;11017](https://redirect.github.com/jdx/mise/pull/11017)) (all by [@&#8203;risu729](https://redirect.github.com/risu729))
- **bootstrap:** Homebrew cask metadata is preserved ([#&#8203;11012](https://redirect.github.com/jdx/mise/pull/11012)) and macOS metadata files in cask artifacts are ignored ([#&#8203;11063](https://redirect.github.com/jdx/mise/pull/11063)); repo origins are compared transport-agnostically so `git@` vs `https://` remotes don't trigger spurious mismatches ([#&#8203;11034](https://redirect.github.com/jdx/mise/pull/11034) by [@&#8203;jeremy](https://redirect.github.com/jeremy)); static systemd units no longer break `bootstrap status` ([#&#8203;11056](https://redirect.github.com/jdx/mise/pull/11056) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **backend:** prerelease versions without a separator (like `1.2.3rc1`) are detected correctly ([#&#8203;11032](https://redirect.github.com/jdx/mise/pull/11032) by [@&#8203;jdx](https://redirect.github.com/jdx)); cargo sparse-index version discovery is restored ([#&#8203;11011](https://redirect.github.com/jdx/mise/pull/11011) by [@&#8203;Turbo87](https://redirect.github.com/Turbo87)); go resolves direct package module prefixes ([#&#8203;11054](https://redirect.github.com/jdx/mise/pull/11054) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **install:** `false` install env values are treated as removals ([#&#8203;11033](https://redirect.github.com/jdx/mise/pull/11033) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **lockfile:** platform-specific tool option variants are preserved instead of being collapsed ([#&#8203;10999](https://redirect.github.com/jdx/mise/pull/10999) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **shell:** hyphenated tool names work in shell integrations ([#&#8203;10961](https://redirect.github.com/jdx/mise/pull/10961) by [@&#8203;risu729](https://redirect.github.com/risu729)), and bash `deactivate` no longer errors on empty array expansions under `set -u` ([#&#8203;11026](https://redirect.github.com/jdx/mise/pull/11026) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **shim:** recursion is prevented when the directory env is filtered ([#&#8203;10982](https://redirect.github.com/jdx/mise/pull/10982) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **task:** file tasks resolve source files relative to the task directory ([#&#8203;11073](https://redirect.github.com/jdx/mise/pull/11073) by [@&#8203;JamBalaya56562](https://redirect.github.com/JamBalaya56562)), and tasks generated from templates inherit the template's `output` setting ([#&#8203;11059](https://redirect.github.com/jdx/mise/pull/11059) by [@&#8203;risu729](https://redirect.github.com/risu729))
- **launchd:** `bootout` EIO for not-loaded agents is tolerated on macOS ([#&#8203;10965](https://redirect.github.com/jdx/mise/pull/10965) by [@&#8203;hsbt](https://redirect.github.com/hsbt))
- **nix:** `TZDIR` is set so timezone tests pass in the sandbox build ([#&#8203;11019](https://redirect.github.com/jdx/mise/pull/11019) by [@&#8203;coryzibell](https://redirect.github.com/coryzibell))

##### Performance

- **startup:** \~2ms shaved off every invocation — config lookup no longer compiles \~200 glob patterns per startup (literal filenames are stat'd directly), and the clap command tree is built once instead of twice. `mise version` drops from 8.1ms to 6.6ms, `hook-env` from 10.9ms to 9.3ms. ([#&#8203;11025](https://redirect.github.com/jdx/mise/pull/11025) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **release:** release binaries for linux-x64 and macos-arm64 are now built with profile-guided optimization, trained on a hermetic offline workload covering config load, toolset resolution, `hook-env`, env rendering, tasks, and exec — \~10% faster on hot paths and \~4% smaller binaries ([#&#8203;11031](https://redirect.github.com/jdx/mise/pull/11031) by [@&#8203;jdx](https://redirect.github.com/jdx)). macOS tarballs now target macOS 12 ([#&#8203;11027](https://redirect.github.com/jdx/mise/pull/11027) by [@&#8203;jdx](https://redirect.github.com/jdx)).
- **runtime:** default tokio worker threads are capped at 16, avoiding oversized thread pools on many-core machines ([#&#8203;11029](https://redirect.github.com/jdx/mise/pull/11029) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Refactor

- tar extraction, inspection, and verification are consolidated onto `jdx-tar`, removing the system `tar` fallback and a duplicate tar implementation — consistent archive behavior across platforms ([#&#8203;11028](https://redirect.github.com/jdx/mise/pull/11028) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Registry

- add zmx ([github:neurosnap/zmx](https://redirect.github.com/neurosnap/zmx)) ([#&#8203;11006](https://redirect.github.com/jdx/mise/pull/11006) by [@&#8203;offbyone](https://redirect.github.com/offbyone))
- docker-compose switched from the aqua to the github backend ([#&#8203;10823](https://redirect.github.com/jdx/mise/pull/10823) by [@&#8203;konono](https://redirect.github.com/konono))
- eza switched from the asdf to the vfox backend ([#&#8203;11068](https://redirect.github.com/jdx/mise/pull/11068) by [@&#8203;jdx](https://redirect.github.com/jdx))

##### Documentation

- **templates:** clarify `exec` behavior in dry runs ([#&#8203;11018](https://redirect.github.com/jdx/mise/pull/11018) by [@&#8203;risu729](https://redirect.github.com/risu729))

#### New Contributors

- [@&#8203;Bartok9](https://redirect.github.com/Bartok9) made their first contribution in [#&#8203;11117](https://redirect.github.com/jdx/mise/pull/11117)
- [@&#8203;LukasKnuth](https://redirect.github.com/LukasKnuth) made their first contribution in [#&#8203;11110](https://redirect.github.com/jdx/mise/pull/11110)
- [@&#8203;jeremy](https://redirect.github.com/jeremy) made their first contribution in [#&#8203;11034](https://redirect.github.com/jdx/mise/pull/11034)
- [@&#8203;Turbo87](https://redirect.github.com/Turbo87) made their first contribution in [#&#8203;11011](https://redirect.github.com/jdx/mise/pull/11011)
- [@&#8203;coryzibell](https://redirect.github.com/coryzibell) made their first contribution in [#&#8203;11019](https://redirect.github.com/jdx/mise/pull/11019)

**Full Changelog**: <https://github.com/jdx/mise/compare/v2026.7.7...v2026.7.11>

#### 💚 Sponsor mise

mise is maintained by [@&#8203;jdx](https://redirect.github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools. Development is funded by sponsors.

If mise saves you or your team time, please consider sponsoring at [jdx.dev](https://jdx.dev/sponsors.html). Individual and company sponsorships keep mise fast, free, and independent.

### [`v2026.7.7`](https://redirect.github.com/jdx/mise/releases/tag/v2026.7.7): : Monorepo deps, systemd timers, and OAuth reliability

[Compare Source](https://redirect.github.com/jdx/mise/compare/v2026.7.6...v2026.7.7)

This release extends dependency providers and systemd bootstrap to monorepo and timer-based workflows, and hardens GitHub OAuth token refreshes and cache clearing against concurrent mise processes.

#### Added

- **deps:** experimental `mise deps --monorepo` runs dependency providers across every explicitly configured `[monorepo].config_roots` entry, aligned with `mise install --monorepo`. Provider IDs are qualified by config root (e.g. `//apps/api:uv`) so repeated provider names across subprojects no longer collide, and mise installs the union of tools declared across participating roots before running providers. Plain `mise deps` remains scoped to the current config root. ([#&#8203;10975](https://redirect.github.com/jdx/mise/pull/10975) by [@&#8203;jdx](https://redirect.github.com/jdx))

  ```toml
  monorepo_root = true

  [monorepo]
  config_roots = ["apps/*", "packages/*"]
  ```

  ```bash
  mise deps --monorepo
  mise deps --monorepo --only //apps/api:uv
  ```
- **bootstrap:** manage systemd user timers alongside services via `[bootstrap.linux.systemd.units]`. Timer entries render to `.timer` units with scheduling (`on_boot_sec`, `on_unit_active_sec`, `on_unit_inactive_sec`, `on_calendar`), persistence, and `timers.target` linkage. Services gain optional directives including `type`, `remain_after_exit`, `exec_stop`, start/stop timeouts, `no_new_privileges`, and `private_tmp`. When a unit name switches between service and timer, the stale sibling unit is stopped, disabled, and removed on apply. ([#&#8203;10984](https://redirect.github.com/jdx/mise/pull/10984) by [@&#8203;jdx](https://redirect.github.com/jdx))

#### Fixed

- **github:** serialize OAuth token refreshes across mise processes. Single-use refresh tokens were only guarded by an in-process mutex, so parallel mise runs could burn the same token or overwrite the cache with stale data. The full read-refresh-write cycle now holds a filesystem lock, re-reads the cache after acquiring it, and writes atomically. Rejected refreshes now surface a sanitized error with reauthorization guidance instead of falling back silently to anonymous requests. ([#&#8203;10995](https://redirect.github.com/jdx/mise/pull/10995) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **cache:** `mise cache clear` no longer fails with `DirectoryNotEmpty` when another mise process (e.g. `hook-env`) recreates cache files mid-removal. Removal now retries with bounded backoff and tolerates concurrently recreated entries, while permission and other errors still propagate. ([#&#8203;10993](https://redirect.github.com/jdx/mise/pull/10993) by [@&#8203;jdx](https://redirect.github.com/jdx))
- **github:** skip OS package and installer assets (`.apk`, `.deb`, `.rpm`, DMG/PKG, MSI/MSIX/AppX and sidecars) during automatic asset selection for the `github:`, `gitlab:`, and `forgejo:` backends, so releases containing only unsupported packages now report that no platform asset matches instead of installing an unusable file. Expli

> ✂ **Note**
> 
> PR body was truncated to here.


</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
2026-07-27 07:46:49 -07:00
renovate[bot] 211232d254 chore(deps): update dependency jscodeshift to v17.4.0 (#666) 2026-07-27 07:46:25 -07:00
renovate[bot] 7d1e567cc4 chore(deps): update postgres:18 docker digest to 3a82e1f (#657) 2026-07-22 10:34:32 -07:00
renovate[bot] 0fb7ba1a36 chore(deps): update redis docker digest to 234c902 (#658) 2026-07-22 10:34:20 -07:00
renovate[bot] 64ef6556d0 chore(deps): update dependency @tsconfig/bases to v1.0.26 (#659) 2026-07-22 10:34:06 -07:00
renovate[bot] f7e1739440 chore(deps): update dependency @types/supertest to v7.2.1 (#660) 2026-07-22 10:33:46 -07:00
Will Schurman 🦦 0dd046d043 chore: update command for release process (#655) 2026-07-13 12:56:04 -07:00
renovate[bot] e6fbffcfee chore(deps): update postgres:18 docker digest to 22c89fe (#651) 2026-07-13 10:14:40 -07:00
renovate[bot] 0619bd1738 chore(deps): update dependency @types/node to v24.13.3 (#652) 2026-07-13 10:14:36 -07:00
renovate[bot] 2454e5b91a chore(deps): update dependency jdx/mise to v2026.7.5 (#653) 2026-07-13 10:14:31 -07:00
renovate[bot] a40f0f4574 chore(deps): update dependency typedoc to v0.28.20 (#654) 2026-07-13 10:14:27 -07:00
Will Schurman 2e8707dd32 v0.65.0 2026-07-10 13:03:50 -07:00
Gabe Debes df75910cbd feat: accept array of parent entity classes in cascade deletion rule (#650) 2026-07-09 16:46:02 -07:00
renovate[bot] 06688ff789 chore(deps): update jest (#649) 2026-07-06 09:40:51 -07:00
renovate[bot] 957387b0d3 chore(deps): update dependency jdx/mise to v2026.7.0 (#648) 2026-07-06 09:39:34 -07:00
renovate[bot] cd353dd058 chore(deps): update dependency @types/node to v24.13.2 (#647) 2026-07-06 09:39:14 -07:00
renovate[bot] b604ff7bf2 chore(deps): update dependency uuid to v14.0.1 (#646) 2026-07-06 09:38:47 -07:00
renovate[bot] 795c231dae chore(deps): update postgres:18 docker digest to 4aabea7 (#644) 2026-06-29 08:34:29 -07:00
renovate[bot] f8eaf094b6 chore(deps): update redis docker digest to 2838d55 (#645) 2026-06-29 08:34:18 -07:00
Will Schurman 🦦 f4201afa86 chore: update renovate groups for new linter (#643) 2026-06-22 10:10:49 -07:00
renovate[bot] 4e07e761c0 chore(deps): update dependency oxfmt to v0.55.0 (#641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-22 09:22:40 -07:00
renovate[bot] ab0eb5525f chore(deps): update dependency pg to v8.22.0 (#642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-22 09:20:35 -07:00
renovate[bot] a5e1738f5f chore(deps): update ci workflows (#639) 2026-06-22 09:19:49 -07:00
renovate[bot] 9b8427a211 chore(deps): update dependency ioredis to v5.11.1 (#640) 2026-06-22 09:18:35 -07:00
renovate[bot] 4badcf8a77 chore(deps): update redis docker digest to 0a97239 (#636) 2026-06-15 08:39:47 -07:00
renovate[bot] f0c01e4414 chore(deps): update ci workflows to v2026.6.10 (#637) 2026-06-15 08:39:28 -07:00
renovate[bot] d8a6c1d8bd chore(deps): update dependency prettier to v3.8.4 (#638) 2026-06-15 08:39:07 -07:00
renovate[bot] abe20b9168 chore(deps): update postgres:18 docker digest to 29ee7bb (#635) 2026-06-15 08:38:52 -07:00
renovate[bot] 1c17dc0f37 fix(deps): update dependency koa to v3.2.1 (#634) 2026-06-08 08:44:45 -07:00
renovate[bot] 5bd968f698 chore(deps): update dependency @tsconfig/bases to v1.0.25 (#632) 2026-06-08 08:34:20 -07:00
renovate[bot] 55be94ba64 chore(deps): update dependency ctix to v2.8.1 (#633) 2026-06-08 08:33:44 -07:00
renovate[bot] 2063fc715c chore(deps): update ci workflows (#631) 2026-06-08 08:21:13 -07:00
renovate[bot] 4454602f22 chore(deps): update dependency jdx/mise to v2026.5.18 (#630) 2026-06-01 14:54:22 -07:00
renovate[bot] 42a7912dc5 chore(deps): update redis docker digest to aa049e6 (#629) 2026-06-01 14:54:13 -07:00
renovate[bot] dd8c710e82 chore(deps): update dependency @isaacs/ttlcache to v2.1.5 (#625) 2026-05-25 09:54:24 -07:00
renovate[bot] 2912764a6a chore(deps): update redis docker digest to 4d25e2f (#624) 2026-05-25 09:39:50 -07:00
renovate[bot] 6879bbde9e chore(deps): update dependency @types/koa to v3.0.3 (#626) 2026-05-25 09:39:30 -07:00
renovate[bot] e89e8f7b6d chore(deps): update dependency jdx/mise to v2026.5.15 (#627) 2026-05-25 09:38:55 -07:00
Will Schurman 🦦 d5a36d1748 chore: upgrade oxlint and add rules for previous eslint rules (#623) 2026-05-22 11:09:08 -07:00
Will Schurman 3f10a9e70e v0.64.0 2026-05-22 08:42:44 -07:00
Will Schurman 🦦 5561a6fcae fix: correctness of TSMockitoExtensions isEqualWithEntityAware for SerializableKeyMaps of different length (#614)
# Why

A bug exists where the ts-mockito extension entity aware matcher would report map equality even when the maps were of different lengths (assuming shorter map had all the same elements as first part of longer map).

# How

Fix by preemptively checking length. Also fix recursive call to use `isEqualWithEntityAware` in case nested objects need it.

# Test Plan

Run updated test.
2026-05-21 10:40:28 -07:00
Will Schurman 🦦 f300d8e55a fix: fix possible composite key collision in StubDatabaseAdapter test utility (#613)
# Why

The stub adapters were doing a simple `:` join, so it was possible for two rows to collide in the stub adapter during tests, if value has the delimiter.

# How

Fix is to use JSON.stringify so that only truly same rows map to the same thing.

# Test Plan

Run new tests.
2026-05-21 10:37:27 -07:00
Will Schurman 🦦 ecf1608f7b chore: update comment for IEntityMetricsAdapter for implementors (#612)
# Why

Just a comment update to make it clearer that IEntityMetricsAdapter implementations must not throw since it'll cause adverse effects for the library.

In a follow-up, it may be good to defend against this, but it's not a straightforward fix.

# How

Update comment.

# Test Plan

Proofread.
2026-05-21 10:34:33 -07:00
Will Schurman 🦦 0eb8989c49 fix: pick method crash when object has hasOwnProperty key (#611)
# Why

`pick` was calling `object.hasOwnProperty(prop)`, but if the object had a property `hasOwnProperty`, it'd be used.

# How

Fix is to use the Object prototype's method.

# Test Plan

Run added test.
2026-05-21 10:31:30 -07:00
Will Schurman 🦦 e8299e2db6 fix: fix behavior of AuthorizationResultBasedEntityAssociationLoader when field value is empty string (#610)
# Why

The bug is that if the column in the current entity that contains the referenced entity's `id` is an empty string, the load method would erroneously return null. This correct the behavior to correctly try to load the entity by ID, which correctly fails since the field is invalid.

# How

Fix check.

# Test Plan

Add new test.
2026-05-21 10:28:28 -07:00
Will Schurman 🦦 292d89167e feat: support numeric enums in StrictEnumField (#609)
# Why

Typescript enums come in two flavors: https://www.typescriptlang.org/docs/handbook/enums.html

Previously, the validation here only worked for string enums.

# How

Update logic to make it work for both string and numeric enums.

# Test Plan

Run new test.
2026-05-21 10:25:28 -07:00
Will Schurman 🦦 20219752b9 feat: validate GenericRedisCacheContext (#608)
# Why

#601 exposed another validation gap in assumptions made about cache keys. We need certain assumptions to be true, namely:
- delimiter is nonzero-length (otherwise issue #601 fixes still would exist)
- delimiter doesn't include escape character (otherwise escape breaks)
- ttlSecondsPositive and negative are positive integers
- cacheKeyVersionsToInvalidateFn returns > 0 versions (otherwise silently doesn't invalidate)

# How

Add validation.

# Test Plan

Add test.
2026-05-21 10:22:29 -07:00
Will Schurman 🦦 717d20367f fix: skip when AllowIfAllSubRulesAllowPrivacyPolicyRule is empty (#607)
# Why

In places where a rule list is dynamically composed, it's likely possible for this list to be empty. Due to the way it's written, it would erroneously allow in this case.

# How

Add check for case and skip.

# Test Plan

Add test.
2026-05-21 10:19:32 -07:00
Will Schurman 🦦 46072a37dd fix: correct cycle guard behavior for deletion (#606)
# Why

There is a bug in self-referential (or transitively-self-referential) cascade cycle detection.

From claude:
```
In AuthorizationResultBasedDeleteMutator:

- deleteInternalAsync (line 802) runs the authorize check, calls
processEntityDeletionForInboundEdgesAsync, runs validators/triggers, deletes the row, and schedules
invalidation callbacks and afterCommit triggers.
- The cycle guard is at the top of processEntityDeletionForInboundEdgesAsync (line 932) — if
(processedEntityIdentifiers.has(entity.getUniqueIdentifier())) return;.

Trace a cycle (A↔B cascade-delete):
1. deleteInternalAsync(A, visited=∅) → runs authorize+triggers+DB delete for A →
processEntityDeletionForInboundEdgesAsync(A, visited=∅), marks A visited, finds B, calls
deleteInternalAsync(B, visited={A}).
2. deleteInternalAsync(B, visited={A}) → runs authorize+triggers+DB delete for B →
processEntityDeletionForInboundEdgesAsync(B, visited={A}), marks B visited, finds A, calls deleteInternalAsync(A,
visited={A,B}).
3. deleteInternalAsync(A, visited={A,B}) → runs authorize+triggers+DB delete for A again → only when it gets into
processEntityDeletionForInboundEdgesAsync does the visited check finally short-circuit.

So beforeDelete/afterDelete/beforeAll/afterAll, the database delete, the post-commit invalidation, and
afterCommit triggers all fire twice for A. The DB delete is idempotent (row's already gone) but triggers
with side effects (emit events, write audit rows, etc.) double-fire.
```

# How

Fix is to move cycle detection to to of `deleteInternalAsync`, thus guarding all checks for already-run entities.

# Test Plan

- Run new test
- Existing test was passing due a stale entity being passed into deleter. The why is a bit nuanced. Here's claude:

```
The original test setup

  const categoryA = await CategoryEntity.creator(viewerContext).createAsync();
  const categoryB = await CategoryEntity.creator(viewerContext)
    .setField('parent_category_id', categoryA.getID())
    .createAsync();
  await CategoryEntity.updater(categoryA)
    .setField('parent_category_id', categoryB.getID())
    .updateAsync();

  After these three statements, the database state is:
  - A: {id: uuidA, parent_category_id: uuidB} (after update)
  - B: {id: uuidB, parent_category_id: uuidA}

  But the local variable categoryA still holds the original entity instance from line 1, where
  parent_category_id was null. updateAsync() returns a new entity, but that return value is discarded — the
  local categoryA was never rebound. So categoryA.getAllDatabaseFields() in the test returns {id: uuidA,
  parent_category_id: null}.

  Why the test passed before the fix

  When the test calls CategoryEntity.deleter(categoryA).deleteAsync(), the outermost mutator's this.entity
  is the stale categoryA (with parent_category_id = null).

  Trace through the OLD cycle behavior for CASCADE_DELETE_INVALIDATE_CACHE_ONLY (so skipDatabaseDeletion =
  true on cascade calls):

  1. Outer A mutator runs. this.entity = stale A. Traverses inbound edges of A, finds B.
  2. Middle B mutator runs (cascade). this.entity = freshly-loaded B from DB (correct fields). Traverses
  inbound edges of B, finds A → cascades to A again.
  3. Inner A mutator runs (cycle re-entry). this.entity = freshly-loaded A from DB, with parent_category_id
  = uuidB (the post-update value).

  3. Under the OLD code, the cycle guard at the top of processEntityDeletionForInboundEdgesAsync stops the
  traversal from recursing further, but the inner deleteInternalAsync still runs validators, triggers, and
  registers a post-commit invalidation callback using this freshly-loaded A's fields.

  After commit, the post-commit invalidation callbacks fire:
  - Outer A registers invalidation for {id: uuidA, parent_category_id: null} → invalidates (id, uuidA) only
  (null isn't a cache key).
  - Middle B registers invalidation for {id: uuidB, parent_category_id: uuidA} → invalidates (id, uuidB) and
   (parent_category_id, uuidA).
  - Inner A registers invalidation for {id: uuidA, parent_category_id: uuidB} → invalidates (id, uuidA) and
  (parent_category_id, uuidB).

  The test asserts that (parent_category_id, uuidB) is invalidated, and it was — because of the inner A's
  fresh-loaded fields. The OLD bug (running deletion side effects twice) was accidentally compensating for
  the stale entity reference held by the test.

  Why the test failed after the fix

  With the cycle guard moved to the top of deleteInternalAsync, the inner A call returns immediately without
   registering any invalidation. Only the outer A's invalidation runs, with the stale parent_category_id =
  null, so (parent_category_id, uuidB) is never invalidated → still HIT → assertion fails.

  This isn't a regression in real-world usage. In real code, callers don't keep stale references after
  updating, because:
  - The idiomatic pattern is const x = await Entity.updater(x)...updateAsync() (rebinding).
  - Non-cycle deletions of stale references already produced incomplete invalidation in the OLD code — the
  cascade re-load only happened to "rescue" the cycle path.

  After the fix, cycle and non-cycle paths behave consistently: the entity instance the caller hands to
  deleter() is the source of truth for what gets invalidated.

  The test edit

  const initialCategoryA = await CategoryEntity.creator(viewerContext).createAsync();
  const categoryB = await CategoryEntity.creator(viewerContext)
    .setField('parent_category_id', initialCategoryA.getID())
    .createAsync();
  const categoryA = await CategoryEntity.updater(initialCategoryA)
    .setField('parent_category_id', categoryB.getID())
    .updateAsync();

  categoryA now binds to the return value of updateAsync(), so it carries the post-update fields. The cycle
  behavior remains under test — the assertion that both (parent_category_id, uuidA) and (parent_category_id,
   uuidB) are MISS still verifies that the cascade correctly invalidated B's and A's cache keys. The fix to
  the test reflects correct API usage, and the cycle handling is independently confirmed by the new fires
  delete-side-effects exactly once per entity in a cycle test.
```
2026-05-21 10:16:27 -07:00
Will Schurman 🦦 4ac1868d98 fix: handle potential infinite recursion for canViewerDeleteAsync (#605)
# Why

In the same way that `processEntityDeletionForInboundEdgesAsync` has detection for reference cycles, this preemptive check utility method needs the same detection mechanism.

# How

Add the same detection mechanism as `processedEntityIdentifiers`

# Test Plan

Add test case for this that fails before change, succeeds after.
2026-05-21 10:13:27 -07:00
Will Schurman 🦦 b1ed31b185 feat: clean up transactional dataloader at transaction end (#604)
# Why

One way memory can grow unbounded within a single request is via transactional dataloaders. If a single request executes N transactions, each one accumulates it's own dataloader state.

Though this has the same semantics as regular dataloaders (which belong to companion provider which is scoped to request by application and discarded after response is sent), they can accumulate more.

# How

The fix is to clear the transactional dataloader at the end of the transaction (or nested transaction), no matter whether the transaction succeeded or rolled-back.

# Test Plan

Add test for condition.
2026-05-21 10:10:28 -07:00
Will Schurman 🦦 38ba558080 fix: correct negative caching behavior in GenericSecondaryEntityCache (#603)
# Why

Please describe the motivation for this PR, and link to relevant GitHub issues, forums posts, or feature requests.

# How

How did you build this feature or fix this bug?

# Test Plan

Please describe how you tested this change and how a reviewer could reproduce your test, especially if this PR does not include automated tests!
2026-05-21 10:07:28 -07:00
Will Schurman 🦦 a920ed0090 fix: update cache key generation for secondary caches (#602)
# Why

Similar to #601, it was possible for a misconfigured application to have collisions in cache keys for secondary caches. Because they likely share the same redis instance at the application level, these keys need similar treatment for separating them logically from normal loader keys plus consistent delimiter escaping.

# How

Hoist final key creation from application level to library level and apply consistent delimiter escaping and namespacing.

# Test Plan

Run new test.
2026-05-21 10:04:28 -07:00
Will Schurman 🦦 df49b12a2e fix!: replace makeKeyFn with delimiter to strengthen guarantee of proper escaping (#601)
# Why

Since cache key construction function is application-supplyable, there's potential that it doesn't escape properly, causing potential cache collisions.

# How

Change the API to only allow specifying the delimiter, and hoist code into the package to handle delimiter escape.

# Test Plan

Run new test to ensure no collisions on something that would've previously collided.
2026-05-21 10:01:28 -07:00
Will Schurman 🦦 22d7298c84 fix: prevent collision in companion when entities share same name or same table name (#600)
# Why

There's nothing preventing two entity classes from sharing the same class name. While not common in practice, this preemptively defends against the issue of collisions in companion maps.

Secondarily, table data coordinators could collide, even if they have different flavors of database and cache adapters. Since those are used to construct the coordinator, include their flavors in the map key.

# How

Update key generation code.

# Test Plan

Run new tests.
2026-05-21 09:58:28 -07:00
Will Schurman 🦦 5ef16f4340 chore: remove unnecessary ViewerScopedEntityMutatorFactory (#599)
# Why

ViewerScopedEntityMutatorFactory existed to make both the loader and mutator patterns more aligned. But it doesn't make as much sense for mutators since for update and delete, the viewerContext used is the one from the entity being updated/deleted instead of the one for the viewer scoped factory. So, in practice the one from the factory was only being used for create mutations.

The simplest change is to just remove the ViewerScopedEntityMutatorFactory to reduce confusion.

# How

Remove class.

# Test Plan

CI
2026-05-21 09:56:08 -07:00
renovate[bot] d0e15580cc chore(deps): update dependency ctix to v2.8.0 (#619) 2026-05-21 08:53:55 -07:00
renovate[bot] a7368a26e2 chore(deps): update dependency @tsconfig/bases to v1.0.24 (#620) 2026-05-21 08:49:21 -07:00
renovate[bot] 7c330773bb chore(deps): update dependency oxfmt to v0.50.0 (#621) 2026-05-21 08:48:17 -07:00
renovate[bot] 0e55f8066e chore(deps): update ci workflows (#618) 2026-05-21 08:44:00 -07:00
renovate[bot] 7786c266d9 chore(deps): update postgres:18 docker digest to 8ff36f3 (#616) 2026-05-21 08:43:23 -07:00
renovate[bot] a70b27a136 chore(deps): update dependency @types/node to v24.12.4 (#617) 2026-05-21 08:37:38 -07:00
renovate[bot] 9dda1bb981 fix(deps): update dependency @apollo/server to v5.5.1 (#598) 2026-05-11 08:15:31 -07:00
renovate[bot] 9c7c0de98f chore(deps): update dependency @types/node to v24.12.3 (#597) 2026-05-11 08:15:01 -07:00
renovate[bot] 05f9bcc6ce chore(deps): update redis docker digest to 0c34149 (#596) 2026-05-11 08:14:27 -07:00
renovate[bot] 0b1fdbe207 chore(deps): update dependency ip-address to v10.1.1 [security] (#594) 2026-05-11 08:14:17 -07:00
renovate[bot] 5fc76ead20 chore(deps): update postgres:18 docker digest to 7e32e98 (#595) 2026-05-11 08:14:07 -07:00
renovate[bot] 98eaaa0298 fix(deps): update dependency uuid to v14 [security] (#593) 2026-04-27 09:11:04 -07:00
renovate[bot] 797e33bd7b chore(deps): update dependency oxfmt to v0.46.0 (#592) 2026-04-27 08:29:38 -07:00
renovate[bot] 4c42f7be8f chore(deps): update postgres:18 docker digest to 7848165 (#589) 2026-04-27 08:26:27 -07:00
renovate[bot] 6add3fb767 chore(deps): update redis docker digest to 832d778 (#590) 2026-04-27 08:26:20 -07:00
renovate[bot] 7d7457a98c chore(deps): update dependency jdx/mise to v2026.4.24 (#591) 2026-04-27 08:25:59 -07:00
renovate[bot] fccf3eb0de chore(deps): update dependency oxlint to v1.60.0 (#588) 2026-04-20 09:34:10 -07:00
renovate[bot] 6ea3dae767 chore(deps): update dependency typedoc to v0.28.19 (#585) 2026-04-20 09:29:22 -07:00
renovate[bot] 604353645d chore(deps): update dependency oxfmt to v0.45.0 (#587) 2026-04-20 08:29:49 -07:00
renovate[bot] 7a3a239313 chore(deps): update ci workflows (#583) 2026-04-20 08:27:55 -07:00
renovate[bot] 36c58cfdf9 chore(deps): update dependency typescript to v6.0.3 (#586) 2026-04-20 08:27:39 -07:00
renovate[bot] a0bb5b9874 chore(deps): update dependency prettier to v3.8.3 (#584) 2026-04-20 08:26:34 -07:00
renovate[bot] 9d851edf9f chore(deps): lock file maintenance (#581) 2026-04-14 16:57:22 -07:00
Will Schurman 🦦 4519909be8 fix: upgrade knex and remove undefined value bindings (#582) 2026-04-14 16:46:07 -07:00
renovate[bot] 1365b11ccb chore(deps): update dependency typescript to v6 (#580) 2026-04-13 14:06:33 -07:00
renovate[bot] 226e1bfb98 chore(deps): update codecov/codecov-action action to v6 (#579) 2026-04-13 13:20:24 -07:00
renovate[bot] b4ed49b289 chore(deps): update dependency oxlint-config-universe to v0.0.3 (#574) 2026-04-13 13:10:00 -07:00
renovate[bot] 5df53a0a7f chore(deps): update dependency prettier to v3.8.2 (#575) 2026-04-13 13:09:51 -07:00
renovate[bot] b8a1f97ea8 chore(deps): update dependency jdx/mise to v2026.4.11 (#573) 2026-04-13 13:09:27 -07:00
renovate[bot] fbd06e4368 chore(deps): update dependency oxfmt to v0.44.0 (#576) 2026-04-13 13:08:56 -07:00
renovate[bot] 4c569b6012 chore(deps): update dependency oxlint-tsgolint to v0.20.0 (#577) 2026-04-13 13:07:30 -07:00
renovate[bot] f039e71041 fix(deps): update dependency koa to v3.2.0 (#578) 2026-04-13 13:06:46 -07:00
renovate[bot] f60b439939 chore(deps): pin dependencies (#569) 2026-04-13 10:56:13 -07:00
renovate[bot] 570ef6f37d chore(deps): update postgres:18 docker digest to 52e6ffd (#570) 2026-04-13 10:55:56 -07:00
renovate[bot] 26a45d3489 chore(deps): update redis docker digest to 1f07381 (#571) 2026-04-13 10:55:45 -07:00
renovate[bot] 976f6e6434 chore(deps): update dependency @types/node to v24.12.2 (#572) 2026-04-13 10:55:34 -07:00
Will Schurman 🦦 9a4f08e31a feat: add count equivalent for knex loader methods (#565) 2026-04-10 17:45:57 -07:00
Will Schurman 🦦 2240e94d08 chore: use oxfmt for import ordering (#564)
# Why

Part 2 of #559.

# How

Use sortImports, and update lint to also run format for eslint parity.

# Test Plan

`yarn lint`
2026-04-08 15:02:23 -07:00
Will Schurman 🦦 a41a95b38d chore: switch to oxfmt/oxlint (#559)
# Why

oxfmt/oxlint has near parity with prettier/eslint but is about 10x faster (10s -> 1s). 

This uses `oxlint-config-universe` equivalent of `eslint-config-universe` as the base. There are known differences between eslint and oxlint, documented in the readme of `oxlint-config-universe`.

The missing rules which we can live without temporarily in exchange for the speedups:
- `naming-convention` (async suffix, PascalCase, UPPER_CASE)
- `no-restricted-properties` (.only/.skip)
- `no-restricted-exports` (no default exports)
- `tsdoc/syntax`
- object-shorthand
- no-dupe-args, no-octal, no-octal-escape
- n/no-deprecated-api

I've created a task to track: #567.

# How

Have claude convert. Use `oxlint-config-universe` and manually audit resulting config against old eslint config. Note down known differences in #567.

In stacked PR, use oxfmt for import ordering as suggested by readme in `oxlint-config-universe`

# Test Plan

`yarn lint`
2026-04-08 14:59:26 -07:00
renovate[bot] 9b642b31c8 fix(deps): update dependency @koa/router to v15.4.0 (#563) 2026-04-06 11:13:03 -07:00
renovate[bot] 64b646786b chore(deps): update dependency jdx/mise to v2026.4.5 (#561) 2026-04-06 11:11:59 -07:00
renovate[bot] 623ab6cf8a chore(deps): update yarn to v4.13.0 (#562) 2026-04-06 11:11:02 -07:00
renovate[bot] 5988712332 fix(deps): update dependency graphql to v16.13.2 (#560) 2026-04-06 11:10:45 -07:00
renovate[bot] 1ed43ddaa5 chore(deps): update dependency lodash-es to v4.18.1 [security] (#557) 2026-04-06 11:10:16 -07:00
James Ide dbd1cc8479 v0.63.0 2026-04-02 17:46:56 -07:00
Will Schurman 🦦 977b790ba7 feat!: remove deprecated loadManyByRawWhereClauseAsync (#556) 2026-04-02 08:11:41 -07:00
Will Schurman 🦦 2693fbc000 fix: remove unnecessary RETURNING * from database adapter update (#555) 2026-04-02 08:11:24 -07:00
renovate[bot] 4709f53b45 chore(deps): update redis docker digest to 009cc37 (#548) 2026-03-30 10:40:28 -07:00
renovate[bot] f4496b9cc7 chore(deps): update ci workflows (#549) 2026-03-30 10:40:09 -07:00
renovate[bot] 15ebe7d584 chore(deps): update dependency ioredis to v5.10.1 (#550) 2026-03-30 10:39:59 -07:00
renovate[bot] 0d00605a93 chore(deps): update dependency lerna to v9.0.7 (#551) 2026-03-30 10:39:38 -07:00
renovate[bot] 73d45fdca3 chore(deps): update node.js to v24.14.1 (#553) 2026-03-30 10:38:40 -07:00
renovate[bot] 3421a746be chore(deps): update dependency typedoc to v0.28.18 (#552) 2026-03-30 10:38:28 -07:00
renovate[bot] 5ae965b736 fix(deps): update dependency @apollo/server to v5.5.0 [security] (#547) 2026-03-27 10:41:59 -07:00
David Mokos bfa703e4ed fix: expose fieldName and fieldValue on EntityInvalidFieldValueError (#546) 2026-03-27 04:58:19 -07:00
Will Schurman 🦦 174d6c602b feat: expose full transaction config on EntityTransactionalQueryContext (#545) 2026-03-25 09:06:27 -07:00
renovate[bot] 0c0c6a3496 chore(deps): update redis docker digest to 315270d (#542) 2026-03-23 09:30:28 -07:00
renovate[bot] 3f103e1dbd chore(deps): update postgres:18 docker digest to a9abf42 (#541) 2026-03-23 09:29:54 -07:00
renovate[bot] d90dce72c2 chore(deps): update ci workflows (#543) 2026-03-23 09:29:21 -07:00
renovate[bot] d800024283 chore(deps): update dependency @types/koa to v3.0.2 (#544) 2026-03-23 09:29:06 -07:00
Will Schurman 🦦 b6c950900d chore: add docblocks for some things (#539) 2026-03-18 16:03:34 -07:00
Will Schurman 4965cc2388 v0.62.0 2026-03-16 11:45:34 -07:00
Will Schurman 🦦 e3cee07091 fix: only throw EntityDatabaseAdapterPaginationCursorInvalidError from cursor decoding logic (#537)
# Why

Noticed that this could throw a TypeError if the decoded JSON wasn't an object:

> TypeError: Cannot use 'in' operator to search for 'id' in blah

# How

Add check that this is an object type ahead of using `in` operator.

# Test Plan

Add new tests for this, run them.
2026-03-16 11:36:22 -07:00
Will Schurman 🦦 0a1d1b5972 fix: remove unused arg from EntityInvalidationUtils (#536)
# Why

This argument was unused, remove it.

# How

Was checking whether EntityInvalidationUtils was the same instance (or same behaviorally) across multiple entities backed by the same table (share a config, different TSelectedFields). Being able to remove this arg proves it since the other args aren't entity-type-specific and are only configuration-specitic.

# Test Plan

`yarn tsc`
2026-03-16 11:33:23 -07:00
renovate[bot] db887d3d7d chore(config): migrate Renovate config (#538) 2026-03-16 11:31:59 -07:00
Will Schurman 🦦 2214f7d19a chore: add docblocks for SQLExpression overload methods (#534)
# Why

Noticed while using these methods in the Expo server application that they were missing docblocks due to vscode preferring the docblock for the overload over the object property.

# How

This adds overload-specific docblocks to ensure they are present in vscode, and since they're overload-specific they are more accurate as well.

# Test Plan

`yarn tsc`
2026-03-16 11:29:57 -07:00
Will Schurman 🦦 4f4243d314 chore: update renovate to batch and schedule to decrease attention cost (#528) 2026-03-16 11:28:09 -07:00
renovate[bot] a505216608 chore(deps): lock file maintenance (#535) 2026-03-15 20:26:52 -07:00
renovate[bot] bc40501535 chore(deps): update dependency jdx/mise to v2026.3.9 (#532) 2026-03-15 11:34:32 -07:00
renovate[bot] 790e75160b chore(deps): update dependency lerna to v9.0.6 (#533) 2026-03-15 11:34:06 -07:00
renovate[bot] cd38019789 chore(deps): update jdx/mise-action action to v4 (#531) 2026-03-13 10:53:04 -07:00
renovate[bot] e1c47de3a7 chore(deps): update jest monorepo to v30.3.0 (#530) 2026-03-13 09:04:20 -07:00
Will Schurman a6f1dd7232 v0.61.0 2026-03-12 12:34:47 -07:00
Will Schurman 🦦 112d4dc9b3 feat: update the SQLExpression and SQLFragmentHelpers APIs (#516) 2026-03-11 16:06:29 -07:00
renovate[bot] f33d388b48 chore(deps): update dependency jdx/mise to v2026.3.8 (#529) 2026-03-11 16:05:28 -07:00
Nick Novitski f138841a59 chore: run jest without babel 2026-03-11 13:02:25 -07:00
Nick Novitski 090c046bbc chore: use combined @tsconfig/bases package 2026-03-11 08:29:23 -07:00
Nick Novitski 81906aed16 chore: Run tests with ESM 2026-03-11 08:25:40 -07:00
Nick Novitski ea9c45fade chore: replace lodash with lodash-es 2026-03-11 08:21:24 -07:00
Nick Novitski 2fc88cb204 chore!: ESM 2026-03-11 08:17:28 -07:00
Nick Novitski e4133a865e chore: generate barrel files with file endings 2026-03-11 08:14:24 -07:00
Nick Novitski f75ed7f749 chore: allow relative imports with ts file extension 2026-03-11 08:11:07 -07:00
renovate[bot] 6bfde96bc3 chore(deps): update dependency jdx/mise to v2026.3.7 (#527) 2026-03-10 18:43:42 -07:00
Nick Novitski 575d8545f7 chore: enable consistent-type-imports lint rule 2026-03-10 09:50:21 -07:00
Nick Novitski 1aae31b0bf chore: use @expo/nullthrows in tests 2026-03-09 17:37:34 -07:00
renovate[bot] 6cbe50a048 chore(deps): update dependency eslint to v9.39.4 (#525) 2026-03-09 16:59:01 -07:00
renovate[bot] 0b4385698c chore(deps): update dependency jdx/mise to v2026.3.6 (#515) 2026-03-09 14:39:22 -07:00
renovate[bot] 1a8535f983 fix(deps): update dependency graphql to v16.13.1 (#510) 2026-03-09 11:18:02 -07:00
renovate[bot] 1f66b9f8f3 chore(deps): update dependency @types/node to v24.12.0 (#512) 2026-03-09 11:17:08 -07:00
renovate[bot] 6a07e6bd66 chore(deps): update dependency pg to v8.20.0 (#511) 2026-03-09 11:16:20 -07:00
Nick Novitski 9202ecbb23 chore: remove @types/uuid package
This is outdated; `uuid` ships with types.
2026-03-09 11:06:52 -07:00
Nick Novitski a3085ed911 Fix renovate updates
Mise can manage yarn versions, but the free renovate doesn't know to use
that configuration.  After this commit, the yarn version is set by the
packageManager field in the root package.json file, and downloaded by
corepack in all environments.
2026-03-09 11:06:33 -07:00
renovate[bot] 934e5f3199 chore(deps): update dependency jdx/mise to v2026.3.5 (#509) 2026-03-08 22:20:26 -07:00
Will Schurman e36dc81bd2 v0.60.0 2026-03-06 09:24:05 -08:00
renovate[bot] 2fc9d9c196 chore(deps): update jdx/mise-action action to v3.6.3 (#508) 2026-03-06 09:22:15 -08:00
Will Schurman 🦦 535b6811e0 feat: Add SQLExpression class that supports fluent comparison method API (#506) 2026-03-06 08:58:32 -08:00
renovate[bot] 792c6c851b chore(deps): update dependency ctix to v2.7.5 (#507)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [ctix](https://imjuni.github.io/ctix) ([source](https://redirect.github.com/imjuni/ctix)) | [`2.7.2` → `2.7.5`](https://renovatebot.com/diffs/npm/ctix/2.7.2/2.7.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/ctix/2.7.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/ctix/2.7.2/2.7.5?slim=true) |

---

### Release Notes

<details>
<summary>imjuni/ctix (ctix)</summary>

### [`v2.7.5`](https://redirect.github.com/imjuni/ctix/releases/tag/v2.7.5)

[Compare Source](https://redirect.github.com/imjuni/ctix/compare/v2.7.4...v2.7.5)

#### What's Changed

- fix(creating): correct exclusion filter logic in filename processing by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;190](https://redirect.github.com/imjuni/ctix/pull/190)
- chore(version): bump version to 2.7.5 by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;191](https://redirect.github.com/imjuni/ctix/pull/191)

**Full Changelog**: <https://github.com/imjuni/ctix/compare/v2.7.4...v2.7.5>

### [`v2.7.4`](https://redirect.github.com/imjuni/ctix/releases/tag/v2.7.4)

#### What's Changed

- fix(creating): resolve duplicate content in index.ts files during create mode by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;185](https://redirect.github.com/imjuni/ctix/pull/185)
- chore(version): bump version to 2.7.3 by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;186](https://redirect.github.com/imjuni/ctix/pull/186)
- fix: resolve duplicate exports and case sensitivity issues by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;187](https://redirect.github.com/imjuni/ctix/pull/187)
- feat(config): remove index.ts file to streamline exports by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;188](https://redirect.github.com/imjuni/ctix/pull/188)
- chore(version): bump version to 2.7.4 by [@&#8203;imjuni](https://redirect.github.com/imjuni) in [#&#8203;189](https://redirect.github.com/imjuni/ctix/pull/189)

**Full Changelog**: <https://github.com/imjuni/ctix/compare/v2.7.2...v2.7.4>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41Ni4wIiwidXBkYXRlZEluVmVyIjoiNDMuNTYuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-03-05 14:19:37 -08:00
renovate[bot] 038b2bff76 chore(deps): update dependency jdx/mise to v2026.3.3 (#505) 2026-03-04 14:57:09 -08:00
renovate[bot] 599fccaf73 chore(deps): update dependency jdx/mise to v2026.3.2 (#504) 2026-03-04 09:10:14 -08:00
renovate[bot] c361dfd7ad chore(deps): update dependency @tsconfig/node24 to v24.0.4 (#503) 2026-03-03 22:26:43 -08:00
renovate[bot] 41bdcf464e chore(deps): update jdx/mise-action action to v3.6.2 (#500) 2026-03-03 22:11:50 -08:00
renovate[bot] a07429fa98 chore(deps): update node.js to v24.14.0 (#501) 2026-03-03 22:11:35 -08:00
renovate[bot] a653ce137c chore(deps): update actions/setup-node action to v6.3.0 (#502) 2026-03-03 22:11:13 -08:00
Nick Novitski 7bea8fbccf chore: provide node and yarn with mise 2026-03-03 16:30:41 -08:00
Will Schurman bfe1aad035 v0.59.0 2026-03-03 14:33:29 -08:00
Will Schurman 🦦 b931daa9a0 feat: add support to SQLFragment for = ANY(?) (#498) 2026-03-03 14:15:00 -08:00
renovate[bot] 72b3c80d12 fix(deps): update dependency @koa/router to v15.3.1 (#497) 2026-03-03 08:51:43 -08:00
renovate[bot] 57e9e06601 chore(deps): update dependency lerna to v9.0.5 (#496) 2026-03-03 08:15:52 -08:00
renovate[bot] 82978f8364 chore(deps): update dependency @types/node to v24.11.0 (#495) 2026-03-03 08:14:02 -08:00
Will Schurman 7dab49a36d v0.58.0 2026-03-02 12:04:25 -08:00
Will Schurman 🦦 21f2f6f461 fix: escape all matching portions of parts of cacher cache keys (#486) 2026-03-02 10:02:17 -08:00
renovate[bot] 4126dcd8f2 chore(deps): lock file maintenance (#493) 2026-03-02 08:04:18 -08:00
renovate[bot] 6889178e60 chore(deps): update dependency ioredis to v5.10.0 (#494) 2026-03-02 07:54:41 -08:00
renovate[bot] a0d3d30265 chore(deps): update dependency @types/node to v24.10.15 (#491) 2026-03-02 07:54:05 -08:00
renovate[bot] d0825c0446 chore(deps): update redis docker digest to 1c054d5 (#492) 2026-03-02 07:51:59 -08:00
renovate[bot] e8440f11c0 chore(deps): update dependency eslint-plugin-tsdoc to v0.5.2 (#488) 2026-03-02 07:51:43 -08:00
renovate[bot] baa1674629 chore(deps): update dependency @types/supertest to v7 (#490) 2026-03-02 07:51:24 -08:00
renovate[bot] 25520c68e8 chore(deps): update dependency pg to v8.19.0 (#489) 2026-03-02 07:50:48 -08:00
renovate[bot] 905e8522b3 chore(deps): update postgres:18 docker digest to 69e8582 (#487) 2026-03-02 07:50:20 -08:00
Will Schurman 🦦 e263bd218c fix: fix some security review suggestions (#485) 2026-02-27 17:59:51 -08:00
Will Schurman e876cfb27b v0.57.0 2026-02-27 17:30:00 -08:00
Will Schurman b9db8a4717 v0.56.0 2026-02-27 17:27:31 -08:00
Will Schurman 🦦 459a5ff850 feat: add entityField SQL helper (#481)
# Why

Asked claude to do an API usability review and it noted that forcing the user to context-switch between entity fields and database fields for SQLFragments was an unfortunate pattern.

# How

This PR provides a SQLFragment helper, `entityField<TFields>('fieldName')` which does the conversion for the user so they don't need to look up the underlying database field.

Secondarily, this provides type safety to ensure SQLFragments for one entity aren't used for SQLFragments of another entity (unless types overlap via shared config).

# Test Plan

Add feature and tests and examples.
2026-02-27 15:44:23 -08:00
Will Schurman 🦦 beda973c0c chore: raw -> unsafeRaw (#480)
# Why

Asked claude to do a security review of entity-database-adapter-knex and renaming `raw` to `unsafeRaw` was one suggestion.

# How

Rename.

# Test Plan

`yarn tsc`
2026-02-27 15:40:10 -08:00
renovate[bot] b258dad2ed fix(deps): update dependency koa to v3.1.2 [security] (#484) 2026-02-27 09:28:08 -08:00
renovate[bot] 976e2c3386 fix(deps): update dependency graphql to v16.13.0 (#479) 2026-02-27 09:02:48 -08:00
Will Schurman 🦦 e32c4c2bcc fix: correct pagination behavior for standard pagination with descending ordering clauses (#477)
# Why

This bug was subtle but important, and can be demonstrated best by looking at the new test case added `performs forward pagination with descending order`.

The bug was that cursor comparison must be derived from both direction but also ASC/DESC of the order bys.

Example:
```
obj1(t=1)
obj2(t=2)
obj3(t=3)
obj4(t=4)

> loadPage(first: 2, orderBy: t DESC)
obj4
obj3

> loadPage(first: 2, orderBy: t DESC, after: obj3)
```

the issue is that the cursor comparison for this second load would be:
```
(t) > (obj3.t)
```
which yields only obj4, which is incorrect.

# How

The fix is to determine cursor comparator by both the order by direction and the pagination direction, thus making the cursor from the example above:
```
(t) < (obj3.t)
```

The second part of this is to require all orderBys to have the same ordering for pagination, otherwise this new assumption breaks since cursor comparison then would be unable to be done with a single comparator. While there are ways we can handle different order by directions, we'd have to not use a cursor which adds a massive amount of complexity.

A small other fix was to set the `id` tiebreaker column order to the same as the other orders.

# Test Plan

Add new test case.
2026-02-26 18:35:23 -08:00
Will Schurman 🦦 2c4c649a4b fix: correct precedence parentheses for SQLFragment joins (#476)
# Why

There's a bug in where clause construction, which used SQLFragmentHelpers.and: https://github.com/expo/entity/blob/main/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts#L303

The bug is that given something like:
```
where: sql`account_id = ${accountId}`,
searchWhere: sql`something ILIKE term OR similarity(term, ...) OR ...`
```
it would produce
```
WHERE account_id = ${accountId} AND something ILIKE term OR similarity(term, ...) OR ...
```

which has the incorrect parentheses and therefore incorrect precedence.

This exposed more bugs in the precedence in `or`, `and`, `join`, etc methods.

# How

The fix is multifold:
- Make the public `join` method only be used for comma-separation joining. Rename it to `joinWithCommaSeparator`.
- Wrap each group in `or` and `and` in parentheses to ensure correct precedence.
    ```
    > SQLFragment.or(sql`account_id = ${accountId}`, sql`app_id = ${appId}`
    sql`(account_id = ${accountId}) OR (app_id = ${appId})`
    ```

# Test Plan

Run existing tests to ensure nothing breaks, update unit tests and inspect output SQL statements to ensure they are correct parentheses. Especially the `builds complex queries with multiple helpers` test case.
2026-02-26 18:31:23 -08:00
Will Schurman 🦦 898f71ce08 feat: add method to get pagination cursor for single entity (#475)
# Why

In a decent number of tests in the Expo server application, it's useful to test pagination from a specific point. It may also be useful for production code in an thus far unbeknownst way.

To do this, one must be able to generate the cursor given an entity. This PR adds such a method.

# How

Add the method that creates a cursor.

# Test Plan

Run the new test.
2026-02-26 18:27:23 -08:00
Will Schurman 🦦 90ae6e299b fix: correct discriminated union type for forward/backward pagination (#474)
# Why

When testing this downstream, I noticed that it was allowed in types to specify first & before / last & after which aren't supported in the pagination.

# How

Fix the types by being explicit about the other fields.

# Test Plan

`yarn tsc`
2026-02-26 18:23:23 -08:00
Will Schurman 🦦 53b0604152 feat: support nullable search fields with postgres transforms (#470)
# Why

#468 added nulls support for regular order bys, but explicitly disabled it for search pagination due to it adding quite a lot of complexity. It also fixed a bug where using a nullable field in pagination in general would break the cursor condition and omit rows (any tuple containing NULL would be dropped due to postgres behavior).

But applications still need to be able to search fields that are nullable, they just need to provide a mechanism for dictating how to cast them as non-nullable. This PR provides such a mechanism.

# How

Allow the user to specify a function to craft the search field SQLFragment from nullable fields. This is required to be a somewhat awkward shape since the library needs to retain control over the field name due to table aliasing in the cursor.

# Test Plan

Run new tests.
2026-02-26 18:19:23 -08:00
Will Schurman 🦦 018a40bc01 fix: Apply same viewer context subclass type mechanism to knexLoader and related methods (#472)
# Why

When these were installed extension functions, we could get away without the `TMViewerContext2` technique. But now that these are loader methods on the `PostgresEntity` subclass, we need this again (it's the same as Entity and ReadonlyEntity).

This type allows the typechecker to behave correctly when a subclass of ViewerContext is used.

# How

Add generic the same as ReadonlyEntity/Entity.

# Test Plan

`yarn tsc` in application that uses ViewerContext subclass and knexLoader.
2026-02-26 18:15:19 -08:00
renovate[bot] 6849468d15 chore(deps): update postgres:18 docker digest to 5558651 (#478) 2026-02-26 17:08:34 -08:00
renovate[bot] c60f4cd455 chore(deps): update dependency @types/lodash to v4.17.24 (#471) 2026-02-26 14:10:28 -08:00
renovate[bot] 820fe88217 chore(deps): update postgres:18 docker digest to a688ae5 (#473) 2026-02-26 14:10:03 -08:00
Will Schurman 🦦 6c0838dc57 feat: add support for nulls ordering in order by clauses (#468) 2026-02-25 15:15:15 -08:00
renovate[bot] 5c11b598c4 chore(deps): update redis docker digest to 5cb00b0 (#467) 2026-02-24 18:47:25 -08:00
Will Schurman 🦦 c946630a82 chore: deprecate loadManyByRawWhereClause (#460)
# Why

Now that `loadManyBySQL` supports all the features of this method (including raw order by), deprecate it.

# How

Mark as deprecated in docblock.

# Test Plan

`yarn tsc`
2026-02-24 16:16:23 -08:00
Will Schurman 🦦 15adeb88cf feat!: convert all raw order bys to use same mechanism and syntax (#459)
# Why

#457 added a new mechanism for specifying ORDER BY clauses using SQLFragment that aligns a lot better with the loader APIs than having separate `orderByRaw`/`orderByFragment` fields in the query modifiers.

# How

Replace all with the new consistent order by in order field, update code to handle that.

One neat side-effect is that we no longer need the hard-to-follow direction-specific ordering in deep methods like `buildTrigramExactMatchPriority` since now we can have those just produce a SQLFragment and reverse the ordering the same as all the other pagination methods.

# Test Plan

Run all tests.
2026-02-24 16:12:25 -08:00
Will Schurman 🦦 9940fcbed2 chore: SQLFragment.toDebugString -> getDebugString (#458)
# Why

Small change to the debug method for SQLFragment. Noticed claude made it a `get toDebugString`, which doesn't quite fit within the patterns in this repo.

# How

Rename to `getDebugString()` method for consistency.

# Test Plan

`yarn tsc`
2026-02-24 16:08:23 -08:00
Will Schurman 🦦 0615ffacff feat: Add raw sqlfragment orderBy capability for pagination (#457)
# Why

One missing piece from pagination is the ability to order by a SQL fragment. This is useful for cases like needing to apply postgres transforms or coalescing to the columns in the order by clause.

Note that this, in combination with `loadBySQL` will completely replace the need for `loadManyByRawWhereClause` (including it's orderByRaw variant), as well as `orderByFragment` in the new pagination code, which is done in a PR upstack.

# How

Make order by specifications either be able to use SQLFragment to specify the field selection but not the ASC/DESC since the framework still needs to retain control of those for pagination purposes.

# Test Plan

Run new added tests.
2026-02-24 16:04:31 -08:00
renovate[bot] 5f3f31bb7c chore(deps): update postgres:18 docker digest to 9b5bd94 (#466) 2026-02-24 16:03:20 -08:00
renovate[bot] 097ac65fc4 chore(deps): update redis docker digest to db01931 (#465) 2026-02-24 16:03:06 -08:00
Will Schurman 🦦 e71ae5fcba feat: Add authorization action to EntityPrivacyPolicyRuleEvaluationContext (#464) 2026-02-24 15:55:28 -08:00
renovate[bot] 3065f360db chore(deps): update node.js to v24.14.0 (#463) 2026-02-24 11:46:37 -08:00
renovate[bot] c3fe15e655 chore(deps): update redis docker digest to dc123d5 (#461) 2026-02-23 17:02:33 -08:00
renovate[bot] 2d41777be0 chore(deps): update dependency eslint to v9.39.3 (#456) 2026-02-23 07:40:49 -08:00
renovate[bot] 45da844a5c chore(deps): lock file maintenance (#455) 2026-02-23 07:36:40 -08:00
Will Schurman 🦦 4dc156c7a8 fix: explicitly document behavior when cursor entity no longer exists (#453)
# Why

The one downside of using id-based cursor pagination and subqueries (added in #422, #431) is that it risks the row referenced by the cursor being deleted between pagination requests. It's an edge case though to be clear. Encoding the full cursor alleviates this, but has it's own downsides since trigram similarity isn't encodable and neither are things like Date objects. It is still preferable to use ID-based cursors for all pagination.

But it becomes the library's responsibility to explicitly document what the behavior is when a row referenced by the cursor is no longer present. This PR does this.

# How

When the cursor row is no longer present, the tuple evaluates to `NULL`, which produces a empty page.

The alternative to this behavior is to run an id check query ahead of the pagination query, and throw an error if it doesn't exist, or even return the first page of data if it doesn't exist. Both of these are less optimal since they cause unexpected behavior during results consumption.

So we keep the behavior as is and explicitly document it.

# Test Plan

Run new tests.
2026-02-17 14:56:23 -08:00
Will Schurman 🦦 cc78029117 fix: eliminate some unnecessary any casts (#448)
# Why

Did a brief audit of `any` casts in the library and removed ones that are no longer needed or could be replaced with a better concrete type.

# How

Grep for `as any`, fix.

# Test Plan

`yarn tsc`
2026-02-17 14:52:24 -08:00
Will Schurman 🦦 517d57b5df fix!: change adaptor -> adapter for consistency (#452)
# Why

As part of #451, claude noticed that we used both american and british english variants of "adapter"/"adaptor". This makes them consistent by making them all "adapter".

# How

Update name. Technically this is a breaking change but the nature of the library allows `tsc` to catch it in all application code that uses the library.

# Test Plan

`yarn tsc`
2026-02-17 14:48:25 -08:00
Will Schurman 🦦 c933db1f8a chore: docblock improvements (#451)
# Why

Asked claude to audit the docblocks for incorrect or missing info. This is what it mentioned as being good improvements.

# How

Update docblocks.

# Test Plan

Proofread.
2026-02-17 14:44:24 -08:00
Will Schurman 🦦 d9377a290b fix: Reorganize constructionUtils to make non-public (#454)
# Why

In #410 we split out `EntityLoaderUtils` into two classes:
- EntityInvalidationUtils - functions for invalidating entity caches from application code when underlying data is mutated outside of the entity framework
- EntityConstructorUtils - functions for constructing and authorizing entities.

Originally, these were both public due to them being the same class. Now, with them separated, we can better restrict access to entity construction which should only be done within the framework itself.

# How

Make constructor utils private everywhere except EntityLoaderFactory, which is deep enough in the abstraction that it's not immediately usable by top-level entity APIs (static methods on Entity/ReadonlyEntity, loader classes).

# Test Plan

`yarn tsc`, CI
2026-02-17 14:40:57 -08:00
Will Schurman 🦦 457f58ad2c fix: update integration test to use BlahEntity.knexLoader syntax (#444)
# Why

Part 4 of #441. This reverts the test changes to use the better pattern, though both work equivalently and are both tested.

# How

Update tests.

# Test Plan

Run tests
2026-02-16 13:26:21 -08:00
Will Schurman 🦦 ed6b2e0553 fix: revert change to codemod now that Entity.knexLoader is supported again via subclass (#443)
# Why

Part 3 of the refactor from #441.

This reverts the codemod back to use the `BlahEntity.knexLoader(vc)` syntax.

# How

`git revert`

# Test Plan

Run the tests in codemod.
2026-02-16 13:22:22 -08:00
Will Schurman 🦦 40b409f6f8 feat: add PostgresEntity/ReadonlyPostgresEntity classes that expose knexLoader methods (#442)
# Why

This is part 2 of the refactor described in #441.

It adds subclasses of Entity and ReadonlyEntity that supply the static `knexLoader` methods to keep the ergonomics of the extension but without the monkey-patching.

# How

Add subclasses, add test.

# Test Plan

Run test.
2026-02-16 13:18:22 -08:00
Will Schurman 🦦 51d632da16 fix: refactor install method to instead be free functions (#441)
# Why

Asked claude extended thinking to analyze the `installExtensions` monkey-patching strategy we went with in  #410 and compare it to other typescript ORM libraries.

It said essentially that it was ok, but most modern libraries choose not to do it that way, and instead recommended "free functions" (TIL about that term) to instantiate the loaders. (`knexLoader(BlahEntity, vc)`)

Working with it even more, I came up with an even better method on top of free functions to keep the `BlahEntity.knexLoader(vc)` syntax. The strategy is to:
1. Convert to free functions (this PR).
2. Add `PostgresEntity`/`ReadonlyPostgresEntity` subclasses that application entities can extend that implement the static extension methods.
3. Revert the codemod change from this PR to keep the codemod to prefer the `BlahEntity.knexLoader(vc)` syntax since most applications should use the subclasses (though it's not strictly necessary if they prefer the free function at callsites).
4. Revert the codemod in the integration tests to express better syntax pattern.

# How

Claude did most of this, but broadly speaking the strategy is to have the free functions, and then instead of patching the prototypes/classes for singleton caching, use a WeakMap where the core package singleton is the key and the value is the new knex-specific object.

# Test Plan

Run all tests.
2026-02-16 13:14:21 -08:00
Will Schurman 🦦 3d4aa0e9ef fix: Add default for TSelectedFields for application-instantiated types (#438)
# Why

When testing an upgrade to this package in the Expo server application, noticed that this was a minor annoyance since TSelectedFields has a default in all other public API methods.

# How

Add default to be consistent.

# Test Plan

`yarn tsc`
2026-02-16 13:10:22 -08:00
Will Schurman 🦦 f5c728be56 fix: pass in augmentable classes to installation (#437)
# Why

An idea proposed by @ide in https://github.com/expo/entity/pull/410#pullrequestreview-3765893898.

This makes it more testable and clearer about what objects installations are "allowed" to augment.

# How

Pass in the classes explicitly.

# Test Plan

`yarn tsc`
2026-02-16 13:06:21 -08:00
renovate[bot] 285f22c461 chore(deps): update dependency typedoc to v0.28.17 (#449) 2026-02-16 13:03:44 -08:00
Will Schurman 🦦 3ec4b5f0cd feat: Add pagination max page size configuration (#436)
# Why

Currently, the Entity pagination system allows clients to request arbitrarily large page sizes using the `first` or `last` parameters in pagination queries. This can lead to:
- Performance degradation when clients request excessively large pages
- Potential memory issues on the server
- Unpredictable query response times
- Difficulty in capacity planning and resource allocation

By adding configurable max page size limits, we can:
- Protect backend services from resource exhaustion
- Ensure predictable performance characteristics
- Provide better control over API usage patterns
- Maintain backward compatibility (no limit by default)

# How

The implementation adds a `paginationMaxPageSize` configuration option to the PostgresEntityDatabaseAdapterProvider that flows through the adapter hierarchy:

1. **Configuration Interface**: Added `PostgresEntityDatabaseAdapterConfiguration` with an optional `paginationMaxPageSize` field
2. **Provider Level**: `PostgresEntityDatabaseAdapterProvider` constructor now accepts the configuration object
3. **Adapter Level**:
   - `BasePostgresEntityDatabaseAdapter` defines a `paginationMaxPageSize` getter (defaults to `undefined`)
   - `PostgresEntityDatabaseAdapter` overrides it to return the configured value
4. **Validation**: `EntityKnexDataManager.loadPageAsync` validates `first`/`last` parameters against the max page size before executing queries

The validation throws descriptive errors when limits are exceeded:
- `"first must not exceed maximum page size of {limit}"`
- `"last must not exceed maximum page size of {limit}"`

Usage example:
```typescript
const provider = new PostgresEntityDatabaseAdapterProvider({
  paginationMaxPageSize: 100
});
```

# Test Plan

Added comprehensive unit tests in `EntityKnexDataManager-test.ts`.
2026-02-16 13:02:21 -08:00
Will Schurman 🦦 f72cbcaf5a feat: Add ilike and trigram similarity search to pagination (#431)
# Why

This PR adds search capabilities to the existing cursor-based pagination system added in #422 which only supported orderBy ordering.

This adds:
1. Case-insensitive pattern matching (ILIKE) - Useful for basic text search across entity fields
2. Trigram similarity search - Provides fuzzy matching capabilities for more advanced search use cases like handling typos or finding similar text

This enables building user-facing search features while maintaining the benefits of cursor-based pagination (stable results, efficient queries).

# How

The implementation introduces a unified PaginationSpecification that supports three strategies:

  1. Standard pagination - The existing orderBy-based pagination
  2. ILIKE search - Pattern matching with automatic wildcard escaping
  3. Trigram search - PostgreSQL trigram similarity with configurable threshold

  Key implementation details:
  - Search terms are properly parameterized to prevent SQL injection
  - ILIKE special characters (%, _, \) are escaped to prevent pattern injection
  - Results maintain stable cursor-based pagination with proper ordering:
    - ILIKE: Ordered by search fields, then ID
    - Trigram: Ordered by exact match priority, similarity score, optional extra fields, then ID

# Test Plan

  The PR includes comprehensive test coverage:

  - Unit tests for both AuthorizationResultBasedKnexEntityLoader and
  EnforcingKnexEntityLoader
  - Integration tests covering:
    - Forward/backward pagination with search
    - Multi-field search
    - Case-insensitive matching
    - Trigram similarity with various thresholds
    - Cursor stability across pages
    - Edge cases (empty results, special characters)
    - Combined with WHERE clauses
    - Security validation ensuring proper escaping and parameterization

All existing tests pass, confirming backward compatibility for standard pagination when using the new API.
2026-02-16 12:58:24 -08:00
renovate[bot] fb720611f0 chore(deps): lock file maintenance (#447) 2026-02-15 23:33:12 -08:00
renovate[bot] 7b8a49c203 chore(deps): update dependency ioredis to v5.9.3 (#445) 2026-02-15 12:09:59 -08:00
renovate[bot] d97707a213 chore(deps): update dependency @types/node to v24.10.13 (#439) 2026-02-13 13:14:27 -08:00
renovate[bot] ced36081b8 chore(deps): update dependency lerna to v9.0.4 (#440) 2026-02-13 13:13:43 -08:00
renovate[bot] b511981a69 chore(deps): update postgres:18 docker digest to b6b4d0b (#435) 2026-02-12 18:03:05 -08:00
Will Schurman 🦦 58deffd426 feat: Add paginated loader to entity-database-adapter-knex (#422) 2026-02-12 11:49:06 -08:00
Will Schurman 🦦 56ec27d5f2 fix: move EntityPrivacyUtils back into core package (#429) 2026-02-11 19:20:26 -08:00
Will Schurman 🦦 be40bcffa6 fix: Type idField in EntityConfiguration as TIDField (#425)
# Why

Noticed this was mis-typed in a later PR, hoisted into this one.

# How

Update type of EntityConfiguration.idField to be `TIDField`, which is the most correct and precise type for the field. This will help later on when adding pagination ID field stuff.

The other changes to tests are fixes since these previously were underspecified and changing the field type exposed these missing types. (claude informed me of this)

# Test Plan

`yarn tsc`
2026-02-11 17:30:23 -08:00
Will Schurman 🦦 a9d09f42b6 fix: Use TSelectedFields for knex loader order by method (#424)
# Why

Noticed while creating an upcoming pagination PR that loaders should use `TSelectedFields` for `orderBy` clauses. This was an accidental omission when the selected fields concept was added.

Succinctly, one should only be able to order entities by their fields rather than by all their underlying table's fields.

# How

Create new orderBy clause type that lives at the loader level. It is fully compatible (subset or equal to) the database orderBy clause, so calling the data manager methods directly with the arguments is by design.

# Test Plan

`yarn tsc`
2026-02-11 17:26:14 -08:00
renovate[bot] a9980b2785 chore(deps): update dependency @types/node to v24.10.12 (#434) 2026-02-11 07:39:14 -08:00
renovate[bot] 7600c7e7d8 chore(deps): update redis docker digest to 7b6fb55 (#433) 2026-02-10 14:35:24 -08:00
renovate[bot] 31e226e22a chore(deps): update node.js to v24.13.1 (#432) 2026-02-10 09:50:20 -08:00
renovate[bot] 66297676e4 chore(deps): update dependency @types/node to v24.10.11 (#428) 2026-02-09 16:14:17 -08:00
Will Schurman 🦦 273d1df8ba feat!: Add sql template and loader method (#414)
# Why

This is the first new feature afforded by the changes in #407.

It adds a feature that is fairly common amongst typescript ORMs: sql via tagged templates for safe value interpolation into SQL statements.

# How

Quite a lot of ORMs have this:
- https://github.com/drizzle-team/drizzle-orm
- https://github.com/mikro-orm/mikro-orm
- https://github.com/blakeembrey/sql-template-tag
- https://github.com/andywer/squid
- https://github.com/Seb-C/kiss-orm

This adds support for it to entity knex.

See tests for examples, especially `PostgresEntityIntegration-test.ts`.

The idea is that this will replace `loadManyByRawWhereClauseAsync` over the course of a few versions.

# Test Plan

Full test coverage.
2026-02-09 16:00:24 -08:00
Will Schurman 🦦 aba0ce5b5f feat: add codemod for upcoming v0.55.0-v0.56.0 (#413)
# Why

This adds a codemod for #407-#410 changes.

# How

Replace `.loader` calls with `.knexLoader` where applicable.

# Test Plan

Run the test.
2026-02-09 15:56:23 -08:00
Will Schurman 🦦 eeccf594c1 feat: add entity-database-adapter-knex-testing-utils containing StubPostgresDatabaseAdapter (#412)
# Why

As of moving raw and conjunction loading into the `entity-database-adapter-knex` package, the stub database adapater is no longer as useful in applications that use the package (it used to support conjunction loading synthetically for unit tests).

# How

This PR adds a new package, `entity-database-adapter-knex-testing-utils` that provides what StubDatabaseAdapter used to be (it has conjunction loading).

The idea is that applications that use `entity-database-adapter-knex` will use this package for their stub database needs and unit test companion provider.

# Test Plan

Full test coverage.
2026-02-09 15:52:23 -08:00
Will Schurman 🦦 e76264c613 chore: add CLAUDE.md and disallow test.skip (#411)
# Why

This commits the claude prompts I have been using to pair program with claude while building this stack.

I also noticed that claude loved to add `test.skip` to make things pass, which now is disallowed as of this PR.

# How

Commit the file.

# Test Plan

yarn lint
2026-02-09 15:48:23 -08:00
Will Schurman 🦦 6fce3a7604 feat!: move knex loaders into entity-database-adapter-knex package (#410)
# Why

Part 3 of #407.

# How

This is the main PR of the stack. It creates a mechanism for "installing" the extension methods on required objects, and then installs them and tests them.

The goal is to make it better self-contained about what is core in the library and guaranteed to be efficient (dataloader'd and cached) and what is not (raw SQL and SQLFragment loads in a future PR).

This is becoming more critical in a time of agentic coding where having a discrete set of loaders that can be linted separately and instruct agents to run `EXPLAIN ANALYZE` in raw cases.

# Test Plan

Full test coverage.
2026-02-09 15:44:24 -08:00
Will Schurman 🦦 567bb61172 chore: move knex-specific loader logic out of EntityDataManager (#408)
# Why

Part 2 of #407.

# How

Refactor methods into a separate class for future move out of main package.

# Test Plan

Full test coverage.
2026-02-09 15:40:23 -08:00
Will Schurman 🦦 2590b95734 feat!: Move knex-specific loader methods to knexLoader (#407)
# Why

This is a long stack, but the goal is to make a place to put postgres-specific loading logic to avoid polluting the core dataloader-based library with logic specific to knex/postgres. And potentially even postgres-specific mutation logic but that's way down the road.

The best place for this stuff is in the `entity-database-adapter-knex` library. Therefore, the strategy is (in PR order):
1. Move existing knex/postgres-specific loader methods to a new loader, `knexLoader`. I'm also open to calling this a "Postgres Loader" since the `entity-database-adapter-knex` package already uses postgres-specific queries (and more are added here since this stack eventually produces raw queries) within knex even though technically knex is database agnostic.
1. Move knex-specific logic out of EntityDataManager, move it to its own EntityKnexDataManager.
1. Move these two now-separated pieces into the `entity-database-adapter-knex` package. This requires creating a "plugin" system for database adapters, where when they are included their methods are added to the prototype so that seamless loading continues to work.
1. Add a codemod for these three pieces.
1. Add the first new feature to entity loading, load by tagged template string, which creates a better/safer way to express raw queries for loading entities.

Future other things that could be added here (after thorough thought on authorization) are:
- Pagination
- Create, update, delete by tagged template string
- Batch creation
- Upsert (maybe)

# For Reviewers

The most critical things to assess are:
- "install" method in #410
- sql tagged template API usability in #414

# How

This is part 1. It refactors the loaders by moving `loadManyByFieldEqualityConjunctionAsync`, `loadFirstByFieldEqualityConjunctionAsync`, and `loadManyByRawWhereClauseAsync` to a separate loader.

The new pattern for these is:
```
await PostgresTestEntity.knexLoader(vc1).loadManyByRawWhereClauseAsync(...)
```

# Test Plan

This has full coverage in new tests.
2026-02-09 15:36:48 -08:00
renovate[bot] bc74562bf9 chore(deps): update dependency @types/node to v24.10.11 (#426) 2026-02-08 13:15:10 -08:00
renovate[bot] e37284f54a chore(deps): update dependency @types/node to v24.10.10 (#423) 2026-02-06 12:02:32 -08:00
renovate[bot] 064431924b chore(deps): update postgres:18 docker digest to 1090bc3 (#421) 2026-02-05 09:37:35 -08:00
renovate[bot] cd81c5a5b6 fix(deps): update dependency @apollo/server to v5.4.0 [security] (#420) 2026-02-04 15:41:34 -08:00
renovate[bot] 7b6ff09c68 chore(deps): update redis docker digest to c22af04 (#418) 2026-02-03 14:29:45 -08:00
renovate[bot] ad6c81def2 chore(deps): update babel monorepo to v7.29.0 (#419) 2026-02-03 14:29:23 -08:00
renovate[bot] 2791d9a05f chore(deps): update postgres:18 docker digest to 3bb77a0 (#416) 2026-02-03 09:05:05 -08:00
renovate[bot] c28e89e15c chore(deps): update redis docker digest to e59abdb (#417) 2026-02-03 09:04:53 -08:00
renovate[bot] 9a9374b173 chore(deps): update dependency pg to v8.18.0 (#415) 2026-02-02 15:19:43 -08:00
renovate[bot] 2dda22d761 fix(deps): update dependency @koa/bodyparser to v6.1.0 (#405) 2026-01-28 07:36:17 -08:00
renovate[bot] 9e88cf6aab fix(deps): update dependency @koa/router to v15.3.0 (#406) 2026-01-27 21:36:30 -08:00
Will Schurman de7548680d v0.55.0 2026-01-26 23:18:40 -07:00
Will Schurman 🦦 5db89777c6 feat: add AllowIf[Any|All]SubRulesAllowPrivacyPolicyRule rules (#401)
# Why

One other set of rules we've found useful in the Expo application for expressing complex privacy logic is a boolean any/all rule that composes an array of rules.

This PR upstreams these rules.

# How

Add rules and tests.

# Test Plan

Run tests.
2026-01-26 22:15:24 -08:00
Will Schurman 🦦 f3b575cd00 feat: add EvaluateIfEntityFieldPredicatePrivacyPolicyRule (#400)
# Why

One other rule we've found useful in the Expo application is a simple predicate rule based on an entity field.

This PR upstreams this rule.

# How

Add rule and test.

# Test Plan

Run test.
2026-01-26 22:11:23 -08:00
Will Schurman 🦦 56ed357b92 feat: Add AllowIfInParentCascadeDeletionPrivacyPolicyRule (#399)
# Why

Found this to be useful in the Expo backend application. The use case is that one may wish to permit access for the mostly-programmatic step of nullification of a field in an entity, whether or not any of the other privacy policy rules in the policy allow.

For example, let's say a user creates a build on organization X. Their user ID may be recorded in a foreign key field on the build so that the application can query all builds for that user. But then the user loses access to organization X. Then, they want to delete their user. This will do a cascade set null on the builds initiated by the user, but the user no longer has permission to update those builds via the normal organization membership permission rules, so this explicit allow-if-cascade-rule is used.

# How

Add a generic rule for use in applications.

# Test Plan

Add test.
2026-01-26 22:07:54 -08:00
renovate[bot] b758c4d344 fix(deps): update dependency @apollo/server to v5.3.0 (#403) 2026-01-26 07:32:55 -08:00
renovate[bot] d4c68112dc chore(deps): update dependency prettier to v3.8.1 (#404) 2026-01-26 07:31:21 -08:00
renovate[bot] 23d8b25805 chore(deps): update dependency pg to v8.17.2 (#402) 2026-01-23 14:54:32 -08:00
renovate[bot] 197ebd9120 chore(deps): update dependency lodash to v4.17.23 [security] (#396) 2026-01-22 11:52:51 -08:00
renovate[bot] a8223d994a chore(deps): update actions/checkout action to v6.0.2 (#398) 2026-01-22 11:51:46 -08:00
Will Schurman 🦦 7c2b812ef2 chore: Update release instructions in README (#397) 2026-01-21 15:47:41 -08:00
Will Schurman 283c02eedb v0.54.0 2026-01-21 16:34:20 -07:00
renovate[bot] c70032dc2a chore(deps): update postgres:18 docker digest to 5773fe7 (#385) 2026-01-21 15:23:42 -08:00
renovate[bot] 6c8bdfaf97 chore(deps): update dependency prettier to v3.8.0 (#392) 2026-01-19 10:01:52 -08:00
renovate[bot] acd5d715db chore(deps): update dependency pg to v8.17.1 (#391) 2026-01-19 10:01:35 -08:00
renovate[bot] 6523f07a5b chore(deps): update dependency ioredis to v5.9.2 (#393) 2026-01-19 10:01:05 -08:00
renovate[bot] 38f451fd59 chore(deps): update dependency @types/node to v24.10.9 (#394) 2026-01-19 10:00:54 -08:00
renovate[bot] 43dd87fcba chore(deps): update babel monorepo to v7.28.6 (#389) 2026-01-16 09:34:41 -08:00
renovate[bot] b125ea3473 chore(deps): update dependency @types/node to v24.10.8 (#390) 2026-01-16 09:16:19 -08:00
renovate[bot] 7244f7b591 chore(deps): update redis docker digest to 73dad42 (#386) 2026-01-15 09:01:29 -08:00
renovate[bot] 17b2105a99 chore(deps): update dependency typedoc to v0.28.16 (#387) 2026-01-15 09:00:40 -08:00
renovate[bot] 0659e8f4a2 chore(deps): update actions/setup-node action to v6.2.0 (#388) 2026-01-15 09:00:06 -08:00
Will Schurman 🦦 8fb53f2f49 chore: validate conventional commit PR title in GHA (#384)
# Why

Since this library uses conventional commits to generate changelogs, we want to validate that the PR title is in a valid conventional commit format.

Closes ENG-15958.

# How

Add lint action.

# Test Plan

This only runs once committed to main so we'll need to commit this and then try it out.

I audited the action code at a glance.
2026-01-13 15:52:12 -08:00
renovate[bot] 757393c707 chore(deps): update dependency @types/node to v24.10.7 (#383)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | [`24.10.6` → `24.10.7`](https://renovatebot.com/diffs/npm/@types%2fnode/24.10.6/24.10.7) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/24.10.7?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/24.10.6/24.10.7?slim=true) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/expo/entity).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NC41IiwidXBkYXRlZEluVmVyIjoiNDIuNzQuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-01-13 09:46:44 -08:00
renovate[bot] a75b41a515 chore(deps): update redis docker digest to eab64db (#382) 2026-01-13 09:39:50 -08:00
renovate[bot] 23f121c91e chore(deps): update node.js to v24.13.0 (#378) 2026-01-13 09:39:25 -08:00
renovate[bot] b984fa949e chore(deps): update redis docker digest to 350e485 (#380) 2026-01-13 09:32:38 -08:00
renovate[bot] cb2f4fa8ae chore(deps): update postgres:18 docker digest to fdd16e6 (#379) 2026-01-13 09:32:05 -08:00
renovate[bot] 0a70d083eb chore(deps): update dependency @types/lodash to v4.17.23 (#381) 2026-01-13 09:31:48 -08:00
James Ide 0f719432a9 Replace es6-error with native Error subclassing (1.9x faster) (#370)
Why
===
es6-error uses Object.defineProperty calls to set name, message, and stack on every error instantiation. Modern Node.js (14+) supports native Error subclassing, making this overhead unnecessary.

How
===
- Remove es6-error dependency
- Use native Error class with static blocks to set the prototype name once per class rather than per instance
- Use abstract getters for state and code in the base class, with concrete implementations in subclasses returning narrowed literal types (e.g., `get state(): EntityErrorState.PERMANENT`)
- Pass cause to Error constructor options instead of manually setting it

Abstract getters were chosen over prototype assignment because:
- They use standard TypeScript/JavaScript patterns that are self-documenting
- They don't require `declare` statements to satisfy TypeScript
- They allow narrowed return types (e.g., `EntityErrorState.PERMANENT` instead of `EntityErrorState`)

The name property is set on the prototype via static initializer because Error.prototype.name is conventionally a prototype property, and this matches how native error subclasses like TypeError work.

Benchmark shows ~1.9x faster error instantiation:
- es6-error: ~213k ops/sec
- native: ~400k ops/sec

Test Plan
===
yarn tsc passes. All tests pass
2026-01-12 17:30:52 -08:00
renovate[bot] a9a730cd83 chore(deps): update dependency @types/node to v24.10.5 (#377) 2026-01-12 17:24:29 -08:00
Will Schurman 🦦 544bb7c3f2 feat: synthetically set cascading set-null fields to null during canViewerDeleteAsync evaluation (#376) 2026-01-12 15:30:31 -08:00
Will Schurman 🦦 c38717d237 chore: require eqeqeq lint (#375) 2026-01-11 11:31:58 -08:00
James IdeandWill Schurman 🦦 ea6c128d9c fix: Inline map().filter() loops in EntityLoaderUtils (#369)
Why
===
The map().filter() pattern creates an intermediate array with null values
that are immediately discarded. For an entity with 8 fields, this allocates
an 8-element array where ~25% of values are null, only to filter them out.

Benchmarks show inlining as a for loop is 1.8x faster:
- map().filter(): 7.0M ops/sec
- for loop:       12.7M ops/sec

How
===
Replace two instances of `.map(...).filter(x => x !== null)` with for...of
loops that conditionally push to the result array. This is the same pattern
used elsewhere in the codebase (e.g., successfulResults, failedResults in
entityUtils.ts).

Test Plan
=========
All existing tests pass (362/362). Behavior is identical.

Co-authored-by: Will Schurman 🦦 <[email protected]>
2026-01-11 11:31:00 -08:00
Will Schurman 🦦 1cc57eddce fix: run tsc during tests CI job (#373) 2026-01-11 10:47:02 -08:00
James Ide 5dff512745 Optimize pick() with direct property access (#368)
Why
===
The previous implementation used Set + Object.entries + Object.fromEntries
which created intermediate data structures. Benchmarks showed this was 5.6x
slower than direct property access for typical usage (2 props from medium
entity: 1.8M vs 10.1M ops/sec).

How
===
Replace the Set-based filtering approach with a simple for...of loop that
directly accesses and copies properties. This avoids:
- Creating an intermediate Set
- Object.entries() array allocation
- Object.fromEntries() reconstruction

Use hasOwnProperty() instead of `in` operator to match the original semantics
(Object.entries only returns own properties) and avoid edge cases with
inherited properties like "toString".

Test Plan
=========
All existing tests pass. The function is semantically identical - it still
returns a new object with only the specified own properties.
2026-01-11 10:38:54 -08:00
renovate[bot] 60fed15603 chore(deps): update dependency ioredis to v5.9.1 (#374) 2026-01-11 09:50:10 -08:00
renovate[bot] fe7f7303aa chore(deps): update actions/setup-node action to v6 (#356) 2026-01-08 16:12:05 -08:00
Will Schurman 🦦 47b167e3bb chore: upgrade docker compose for tests to postgres 18 (#367) 2026-01-08 16:10:53 -08:00
Will Schurman 🦦 3997adf002 chore: upgrade lerna and run breaking change migration (#366) 2026-01-08 16:00:32 -08:00
Will Schurman 🦦 858f8715e6 chore: upgrade to uuid 13 (#365) 2026-01-08 15:45:51 -08:00
renovate[bot] 67f7516489 chore(deps): update dependency uuidv7 to v1.1.0 (#351) 2026-01-08 09:21:49 -08:00
Will Schurman 🦦 7947fe9871 chore: upgrade prettier and apply (#364) 2026-01-08 09:20:36 -08:00
renovate[bot] 5f9485627a chore(deps): update dependency ioredis to v5.9.0 (#349) 2026-01-08 09:20:16 -08:00
renovate[bot] 37fc067970 chore(deps): update yarn to v4.12.0 (#354) 2026-01-08 09:15:48 -08:00
renovate[bot] a6d2d3c55c chore(deps): update node.js to v24.12.0 (#352) 2026-01-08 09:15:15 -08:00
renovate[bot] ece647b31f chore(deps): update dependency lerna to v8.2.4 (#342) 2026-01-08 09:15:04 -08:00
renovate[bot] be72c6bf4b chore(deps): update dependency ctix to v2.7.2 (#341) 2026-01-08 09:11:26 -08:00
renovate[bot] b7f2148654 chore(deps): update actions/setup-node action to v4.4.0 (#345) 2026-01-08 09:11:16 -08:00
renovate[bot] 73f0dfa223 chore(deps): update peaceiris/actions-gh-pages action to v4 (#362) 2026-01-08 09:10:34 -08:00
renovate[bot] 35bf335a5d chore(deps): update dependency typedoc to v0.28.15 (#343) 2026-01-08 09:09:37 -08:00
renovate[bot] 971e73037d chore(deps): update codecov/codecov-action action to v5.5.2 (#346) 2026-01-08 09:09:21 -08:00
renovate[bot] 328d6ac319 chore(deps): update dependency @types/supertest to v6 (#358) 2026-01-08 09:08:08 -08:00
renovate[bot] ea21dc0f76 chore(deps): update dependency @types/jscodeshift to v17 (#357) 2026-01-08 09:07:14 -08:00
renovate[bot] 94808312eb chore(deps): update actions/checkout action to v6 (#355) 2026-01-08 09:06:00 -08:00
renovate[bot] 8998970643 chore(deps): update dependency eslint to v9.39.2 (#348) 2026-01-08 09:05:40 -08:00
renovate[bot] f9441a77a8 chore(deps): update dependency @jest/globals to v30.2.0 (#347) 2026-01-08 09:05:24 -08:00
renovate[bot] 4146afc887 chore(deps): update dependency @types/supertest to v2.0.16 (#340) 2026-01-08 08:57:18 -08:00
renovate[bot] 5b7739691f chore(deps): update dependency @isaacs/ttlcache to v2.1.4 (#333) 2026-01-08 08:56:49 -08:00
renovate[bot] 002bbd5bbf chore(deps): update dependency @tsconfig/strictest to v2.0.8 (#334) 2026-01-08 08:55:54 -08:00
renovate[bot] 9f939269cf fix(deps): pin dependency @koa/bodyparser to 6.0.0 (#339) 2026-01-08 08:55:40 -08:00
renovate[bot] ee6e57fd3b chore(deps): update dependency @types/lodash to v4.17.21 (#338) 2026-01-08 08:55:05 -08:00
Will Schurman 🦦 4cad01a940 chore: upgrade dependencies in entity-example (#332) 2026-01-08 08:51:01 -08:00
renovate[bot] 7038912ca5 fix(deps): pin dependency @apollo/server to 4.12.2 (#330) 2026-01-07 16:35:12 -08:00
renovate[bot] ce55922e3e fix(deps): pin dependency koa to 2.16.3 (#331) 2026-01-07 16:34:48 -08:00
renovate[bot] d8dccb95e4 chore(deps): update dependency koa to v2.16.2 [security] (#327) 2026-01-07 15:07:55 -08:00
renovate[bot] 5679f27209 fix(deps): pin dependencies (#328) 2026-01-07 15:06:14 -08:00
renovate[bot] bcce8ef0ae chore: Configure Renovate (#325) 2026-01-07 15:01:27 -08:00
Will Schurman 833e2e8c55 v0.53.0 2025-12-19 13:24:53 -08:00
Will Schurman 1a7dfadf64 v0.52.0 2025-12-19 13:20:50 -08:00
Will Schurman 🦦 767d9513ff chore: upgrade to node 24 LTS (#323) 2025-12-09 08:10:30 -08:00
Will Schurman 🦦 c4537262d8 feat: switch local cache to an interface (#322) 2025-12-08 17:40:30 -08:00
Will Schurman 9140938e1c v0.51.0 2025-11-21 11:36:24 -08:00
Will Schurman 🦦 019809fbe9 feat!: separate out validators into beforeCreateAndUpdate and add new beforeDelete validators (#320)
# Why

For a while in Expo application code, we've used beforeDelete triggers to approximate beforeDelete validators, but we finally found a need to separate them out. Due to the nature of validators (no side-effects) vs triggers (side-effectful), there can be a need to manually run validators but not triggers. One such need is ahead of batch deletion, to ensure that an object and it's related objects can be deleted and retain any constraints implemented in validator logic.

Closes ENG-18066.

# How

Split out definition to be more similar to triggers, update tests.

# Test Plan

Add new test for these, ensure that they are called appropriately.
2025-11-12 09:08:23 -08:00
Will Schurman 🦦 4559f8fe1b chore: add docblock for orderBy and ordering (#319)
# Why

These were missing docblocks, and are something I often have to look up.

Closes ENG-18067.

# How

Add docblocks.

# Test Plan

tsdoc
2025-11-12 09:03:23 -08:00
Will Schurman 🦦 30f4a62bfa chore: update other sibling dependencies (#318)
# Why

Part 2 of #317, just updates more dependencies. These I check the changelogs for though to be sure it isn't going to cause any issues.

# How

Read changelogs, update.

# Test Plan

CI

Changelogs:
https://github.com/redis/ioredis/blob/main/CHANGELOG.md
https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md
https://github.com/beaugunderson/ip-address/compare/v10.0.0...v10.0.1
2025-11-12 08:58:24 -08:00
Will Schurman 🦦 a1f1934fd7 chore: update dev dependencies (#317)
# Why

Ahead of some library updates, this updates the dev dependencies to their latest versions. Just a chore.

# How

Update.

# Test Plan

CI
2025-11-12 08:54:18 -08:00
Will Schurman 🦦 2f9d4873aa fix: add default for TSelectedFields in PrivacyPolicyTestUtils Case (#316) 2025-11-04 16:37:41 -08:00
Will Schurman 59e024eb19 v0.50.0 2025-10-07 16:12:16 -07:00
Will Schurman 🦦 0f53c24faf fix: make in memory and stub database adapters behave more similarly to sql (#315)
# Why

#311 updated the errors for postgres, but the conditions in the stub adapters preempted those errors in test code, so their behavior diverged. This PR makes the stub adapters behave more similarly to Postgres adapter.

# How

Return more similar results in not-found cases. See inline comments.

# Test Plan

`yarn tsc`. Also the tests in entity-example test this.
2025-10-07 16:08:43 -07:00
Will Schurman 🦦 1b9ddaa95f chore: improve test coverage (#314) 2025-10-07 13:32:23 -07:00
Will Schurman 4ab2cf1cc4 v0.49.0 2025-10-07 09:26:02 -07:00
Will Schurman 🦦 4c3fd175a9 feat: add more documentation to entity errors (#312) 2025-10-06 16:18:35 -07:00
Will Schurman 🦦 f6639f9def feat: throw specific errors for database adapter issues (#311) 2025-10-06 14:32:16 -07:00
Will Schurman 🦦 949f00d93b fix: validate that UUID field is lower-case (#310)
# Why

Technically upper-case UUIDs are valid according to https://datatracker.ietf.org/doc/html/rfc4122.

But the way entity is written expects case sensitivity due to them being treated as simple strings. For example, this would be the expected behavior:
```
const entities = await TestEntity.loader(vc).loadManyByFieldEqualingAsync('id', ['018EBFDA-DC80-782D-A891-22A0AA057D52']);
=> Map(['018EBFDA-DC80-782D-A891-22A0AA057D52', TestEntity[...]])
```

The issue is that Postgres stores them in a case-insensitive manner (which is correct according to the UUID spec). So in actuality, the above code would theoretically do something like:
```
const entities = await TestEntity.loader(vc).loadManyByFieldEqualingAsync('id', ['018EBFDA-DC80-782D-A891-22A0AA057D52']);
=> Map(['018ebfda-dc80-782d-a891-22a0aa057d52', TestEntity[...]])
```
(it actually fails due to this change of case further up in the stack at https://github.com/expo/entity/blob/077d53cd86d4c54132ea2ebcc59d1d75e2b507d0/packages/entity/src/EntityDatabaseAdapter.ts#L182, but the reason is the same: postgres transforms to lower case under-the-hood)

This results in breaking invariants the framework provides (all keys in === all keys out).

This PR solves this problem by just enforcing UUID casing in fields to match what Postgres already does to avoid this issue.

Ref: WWW-37

# How

I first tried a field normalization approach in #309, but this also had the same problem: the fields in !== fields out. Unless we were to invert the normalization on the way out, but that comes with a lot of added complexity.

So this is the solution I propose: just don't allow any upper-case characters in the UUIDs. This is technically not UUID spec compliant since we no longer accept all technically valid UUIDs.

# Test Plan

Run the test.
2025-09-30 12:52:06 -07:00
Will Schurman 🦦 02f92fc6ac chore: fix erroneous describe.only and add lint rule to prevent it (#308)
# Why

In other repositories at Expo we have this lint disallow rule to avoid accidental skips like this. This PR adds the same rule.

# How

1. Add rule.
2. Fix violation.

# Test Plan

`yarn lint`
2025-09-30 12:48:53 -07:00
Will Schurman 🦦 077d53cd86 fix: add readonly modifier to partitionArray utility method (#307) 2025-09-22 10:59:26 -07:00
Will Schurman 7b4bec8f84 v0.48.0 2025-09-19 15:59:11 -07:00
Will Schurman 4c6f340d17 v0.47.0 2025-09-19 12:03:43 -07:00
Will Schurman 🦦 4a3eda209f feat: allow manual specification of cascadingDeleteCause in delete mutator factory method (#306) 2025-09-19 11:36:24 -07:00
Will Schurman 🦦 31b52a5f75 feat: add support for Buffer fields (#305) 2025-07-29 13:26:19 -07:00
Nick Novitski 9a03fdb7ba chore: Clear dependency warnings on entity-example 2025-06-26 17:13:00 -07:00
Nick Novitski 28cbac54b4 chore: Remove unneeded files from packages 2025-06-26 17:12:21 -07:00
Will Schurman 79f61f8d7f v0.46.0 2025-06-26 13:12:05 -07:00
Nick Novitski 0038d633b3 Fix exports for ESM projects
Before this commit, for projects with `"type": "module"`,  anything
exported from a file `export default` was not resolvable from the barrel
file as a named export.

After this change, the interface of the index is unchanged, but ESM
projects can use it.

To test this, the entity-example package is given `"type": "module"`,
and changed to run with `node --experimental-transform-types`.  Its
imports from other packages do not need to change.  The other
integration tests passing are evidence that commonjs modules also do not
need to change their imports.

This commit also adds an  eslint rule to enforce this standard.
2025-06-24 10:03:22 -07:00
Nick Novitski 18a0d297d0 Unify repo-wide scripts
These are faster, and can pass arbitrary arguments to the underlying
command, which allows repo-wide "watch" workflows.
2025-06-24 09:58:54 -07:00
Nick Novitski 93d84807da chore: Use 'v8' coverage provider 2025-06-23 16:04:54 -07:00
Nick Novitski 64057b6d52 chore: Explicitly list files to collect coverage from
By default, files not loaded from any test will not effect coverage
metrics, although actually we want them to be counted as uncovered.
2025-06-23 15:45:59 -07:00
Nick Novitski 850fa2b886 chore: Use tsconfig bases (#296)
# Why

These are sensible defaults, so much so that they almost perfectly match what we already set.

# Test plan

`yarn build`
2025-06-20 12:24:42 -07:00
Nick Novitski 65d50d6cb3 chore: jest v29.7.0 -> 30.0.0 (#295)
# Why

It's faster, at least locally where we run without coverage instrumentation.

# Test Plan

CI.
2025-06-20 12:19:23 -07:00
Nick Novitski 1fd0144cc3 chore: Replace ts-jest with babel-jest (#294)
# Why

It's faster.  We can still type-check in a separate step in CI, or locally.

# Test Plan

All test scripts are still runnable, including in CI.
2025-06-20 12:14:21 -07:00
Nick Novitski 6cecd9f6b9 chore: Remove @jest/types (#293)
# Why

Jest is written in typescript and ships with its own types.

# Test Plan

Typecheck and tests running in CI.
2025-06-20 12:07:30 -07:00
Will Schurman 875cce3432 chore: update README to be more precise (#292) 2025-06-16 21:59:15 -07:00
Will Schurman fe2d246f87 v0.45.0 2025-06-06 09:06:35 -07:00
Will Schurman a3ab61bfa8 feat: add ability to define a custom cache key invalidation version supplier (#290) 2025-06-06 09:01:47 -07:00
Will Schurman 78bc264b33 feat: add createOrGetExistingAsync and createWithUniqueConstraintRecoveryAsync utility methods (#291) 2025-06-06 08:59:59 -07:00
Will Schurman b9b09933e5 v0.44.0 2025-05-29 09:45:25 -07:00
Will Schurman d3e89bdd82 fix: introduce transactional dataloader mode and fix global transaction ID generation (#289) 2025-05-28 17:09:29 -07:00
Will Schurman b6089524bb chore: add more @internal to typedoc (#287)
# Why

Noticed in this stack that a few more places could use the `@internal` typedoc annotation to prevent it from polluting the public docs.

# How

Add annotation to internal constructs.

# Test Plan

Inspect docs manually (`yarn typedoc`, open html file)
2025-05-23 11:07:21 -07:00
Will Schurman e054a0d74b feat: add transaction-scoped dataloaders (#284)
# Why

This is a retry of #98. It has become more important in Expo's server application due to more things running within transactions. When it was written previously, relatively few things were transactional.

The idea is that within a transaction, subsequent reads should be able to be cached locally (dataloader) as long as the local cache is kept consistent (not in the cache adapter though).

# How

The way this is done is to keep a dataloader map (dataloader per-entity like we do for the top-level loads) per-transaction as well. Then, when in a transaction, instead of going directly to the database, go through the transaction's dataloader before hitting the database. This way loads within the transaction are coalesced and cached locally.

Invalidation becomes more complex though. 
- We still only want to invalidate the non-transactional dataloader/cache at the end of the transaction (as late as possible for least chance of race condition) for all changed entities. This didn't change.
- As for when to invalidate the transactional dataloaders, we need to think about this like database sub-transaction visibility, but only in the sense that we don't want inconsistent reads. Put another way, any load in or out of any transaction or nested transaction needs to be consistent with what is in the database, so any time the loaded value would change if it went directly do the database we must also invalidate the cache.
   - This is further complicated by the fact that postgres doesn't have real subtransactions (see #194 for more discussion) and subtransactions weren't an entity feature for the first attempt at this. This means that it's hard to test the case where a nested transaction is isolated from the parent transaction for uncommitted reads or the inverse.
   - The way I came up with achieving this is to just to over-invalidate: clear all the dataloaders in the transaction recursively starting from the outermost transaction. And do this clearing right after the mutation and at the end of the sub-transaction and at the end of the transaction (recursively).

# Test Plan

Run new tests.
2025-05-23 11:02:22 -07:00
Will Schurman 4ddea7da77 chore: upgrade dataloader package (#286)
# Why

Noticed that this (major) dependency of entity has been updated. The [changelog](https://github.com/graphql/dataloader/blob/main/CHANGELOG.md) doesn't indicate anything breaking.

# How

Upgrade.

# Test Plan

Rely upon full test coverage for compatibility.
2025-05-23 10:55:22 -07:00
Will Schurman 8c52ace8d1 chore: upgrade dev dependencies and eslint (#285)
# Why

This upgrades some dev dependencies and eslint to v9.

# How

Upgrade packages, [update eslint config to be flat config.](https://eslint.org/docs/latest/use/configure/migration-guide)

# Test Plan

CI (lint).
2025-05-23 10:48:27 -07:00
Will Schurman 618383027d v0.43.0 2025-04-10 14:22:20 -07:00
Will Schurman 9758fd5df8 fix: fix generated tsc definition sentinel type visibility (#283) 2025-04-10 14:12:55 -07:00
Will Schurman 8414d96d94 v0.42.0 2025-04-10 12:39:21 -07:00
Will Schurman ed8528d6a6 chore: upgrade dependencies (#282) 2025-04-10 11:24:50 -07:00
Will Schurman 0ea917ad02 chore: separate out build tsconfig (#281) 2025-04-09 09:16:26 -07:00
Will Schurman 485894af16 feat: add @expo/entity-testing-utils package (#280) 2025-04-09 09:11:36 -07:00
Will Schurman 1da5cc01e1 feat: enforce explicit id field cache property (#276) 2025-04-02 10:53:25 -07:00
Will Schurman b7e524c418 feat: change entity id field generic to field name and derive type where necessary (#278) 2025-04-02 10:48:47 -07:00
Will Schurman d9c1852706 feat: invalidate n+/-1 cacheKeyVersion for entities for push safety (#275) 2025-04-02 10:31:15 -07:00
Will Schurman ca1b69e2cb chore: upgrade typescript to 5.8 and a few other packages (#274) 2025-03-30 11:32:50 -07:00
Will Schurman f0aa0dafeb feat: add composite field loading and caching (#272) 2025-03-30 11:16:38 -07:00
Will Schurman 06b3cb7f80 feat: convert batched/cached loader interface to holder pattern (#271)
# Why

Step 2 of #201.

This PR abstracts out (genericizes) the load key and load value interfaces for batched/cached loading in preparation for composite field loading.

# How

Accomplishing this requires a number of things:
1. A set of interfaces for the keys and load values to be used to load entity field objects.
2. A concrete implementation of that interface for the current use case (single fieldName, fieldValue(s)).
3. Data structure changes in the adapters and data manager to handle the new key type not being comparable via `===`. (see base PR for new map data structure for using these objects as keys).
4. Updates to the test matchers to know how to compare the serializable holder objects.

# Test Plan

Update a number of tests and add a few new tests. This has full coverage.
2025-03-28 11:36:10 -07:00
Will Schurman ad41af1c82 feat: add some utility classes and methods in preparation for composite keys (#269)
# Why

This adds the utility classes needed for composite field loading as described in #201.

# How

This adds a few concepts:
- `areSetsEqual` - simple utility for checking set equality
- `SerializableKeyMap` - a map where keys must be of a custom interface for checking equality.

# Test Plan

Add new tests, run them. Full coverage.
2025-03-28 11:32:12 -07:00
Will Schurman f3a2bf62de chore: update generic constraint for TFields everywhere (#268) 2025-03-22 09:57:08 -07:00
Will Schurman 1d95eba1f9 fix: fix metrics for dataloader waterfall logging (#267) 2025-03-22 09:41:39 -07:00
Will Schurman aa56b705db fix: move source of truth of loader docblocks to enforcing loader since it is not the default loader (#264) 2025-03-22 08:58:24 -07:00
Will Schurman ee4be736a9 v0.41.0 2025-03-07 11:43:14 -08:00
Will Schurman 4a2b61f2bd feat: Create codemod for v0.40.0 -> v0.41.0 (#260) 2025-03-07 11:39:22 -08:00
Will Schurman a976e76509 feat!: Default all loaders and mutators to enforcing (#256)
BREAKING CHANGE: Default all loaders and mutators to enforcing
2025-03-07 11:34:13 -08:00
Will Schurman 72f34f0bee chore: Fix changelogs for 0.40.0, update readme 2025-02-26 22:59:51 -08:00
Will Schurman 74c396255c v0.40.0 2025-02-26 22:50:49 -08:00
Will Schurman 5cb88ca6e7 chore: remove prepublish script 2025-02-26 22:43:23 -08:00
Will Schurman 4d070248d8 chore: fix typedoc 2025-02-26 22:29:01 -08:00
Will Schurman acd52f18ce chore: fix build setup (#263) 2025-02-26 22:23:22 -08:00
Will Schurman d4eb0a6214 chore: attempt to fix lint again 2025-02-26 21:44:08 -08:00
Will Schurman 3cd004ad55 chore: attempt to fix lint in CI 2025-02-26 21:41:34 -08:00
Will Schurman 381651d8bb chore: fix lint 2025-02-26 21:29:34 -08:00
Will Schurman 089cfbb040 chore: fix yarn v4 dev dependencies 2025-02-26 21:24:34 -08:00
Will Schurman 02f7428257 chore: upgrade to yarn 4.6.0 2025-02-26 09:55:39 -08:00
Will Schurman 26ad65d657 chore: upgrade lerna 2025-02-26 09:11:40 -08:00
Will Schurman fa78ce84e2 Revert "v0.40.0"
This reverts commit c9ab838a75.
2025-02-25 09:59:55 -08:00
Will Schurman c9ab838a75 v0.40.0 2025-02-25 09:55:05 -08:00
Will Schurman 3c2a1920f0 chore: fix typedoc again (#262) 2025-02-25 09:38:34 -08:00
Will Schurman ab34b4ae8a chore: fix typedoc (#261) 2025-02-25 07:57:08 -08:00
Will Schurman d63ddd6599 feat: Create codemod for v0.39.0 -> v0.40.0 (#259) 2025-02-24 12:09:24 -08:00
Will Schurman c6f74a14a9 feat!: Add enforcing entity association loader (#257) 2025-02-23 19:35:25 -08:00
Will Schurman be9c40e8d7 chore: add CI check for needing to run ctix (#258) 2025-02-23 19:32:25 -08:00
Will Schurman 081c3576a8 chore: switch barreling to ctix (#255) 2025-02-23 19:29:25 -08:00
Will Schurman 92bac05687 chore: internal refactor of loader to be structured like new mutation methods (#254) 2025-02-23 19:26:22 -08:00
Will Schurman 4d56065518 feat!: Make load/create/update/delete entity authorization result interfaces consistent (#253) 2025-02-23 19:23:25 -08:00
Will Schurman 275c63d277 chore: upgrade typescript and related packages (#252) 2025-02-23 19:20:47 -08:00
Will Schurman 2504aecddb chore: update pg package to latest version (#251) 2024-12-04 10:01:29 -08:00
Will Schurman 9426a08a32 v0.39.0 2024-11-21 10:46:32 -08:00
Will Schurman ae3a6c5c77 chore: upgrade typescript and lint (#250) 2024-11-21 10:45:15 -08:00
Will Schurman 27a358d259 feat: add function to get authorization results for canViewerUpdateAsync/canViewerDeleteAsync (#249) 2024-11-20 14:32:00 -08:00
Will Schurman c2ae21b5cc chore: upgrade typescript to 5.5.4 (#247) 2024-08-21 13:45:21 -07:00
Will Schurman 5ab1f7953c chore: upgrade typescript and related packages (#246) 2024-07-01 09:52:04 -07:00
Will Schurman d7cc0c23b9 v0.38.0 2024-06-27 12:45:48 -07:00
Will Schurman 162b19257a feat: add EntityEdgeDeletionAuthorizationInferenceBehavior for canViewerDeleteAsync (#243) 2024-06-27 12:33:41 -07:00
Will Schurman 15117fc85e chore: add linter for async method Async suffix (#242) 2024-06-14 14:55:10 -07:00
Will Schurman 59fc77810a v0.37.0 2024-06-12 09:56:29 -07:00
Will Schurman 8c5f81bcca fix: update barrels (#241) 2024-06-12 09:54:39 -07:00
Will Schurman 28e3afb62b chore: update CHANGELOG.md (#240) 2024-06-11 21:35:08 -07:00
Will Schurman f17a33c428 v0.36.0 2024-06-11 21:29:05 -07:00
Will Schurman 93905be81c chore: move entity loader utils into their own object (#239) 2024-06-11 21:04:20 -07:00
Will Schurman 2edc7af7e2 BREAKING CHANGE: make using non-enforcing entity loader explicit (#238) 2024-06-11 20:50:49 -07:00
Josh McFarlin 6569486ecd feat: Add global mutation trigger field to EntityCompanionProvider (#215)
* Add global mutation trigger field to EntityCompanionProvider

* Add globalMutationTrigger to additional test cases

* merge mutation triggers outside of EntityMutator

* Exclude triggers with empty arrays
2024-06-11 12:24:16 -07:00
Will Schurman 778d9e304e chore: upgrade dev dependencies and knex (#237) 2024-06-04 16:01:56 -07:00
Will Schurman 05726d45fa fix: disallow keys of JS Object prototype for safety (#236) 2024-06-04 15:41:13 -07:00
Will Schurman 7e2cea1697 fix: constrain entity fields type to string-keyed object (#235) 2024-06-04 15:35:13 -07:00
Will Schurman 7c3c9854a4 fix: always reload entity after update since cascading changes may have changed it since commit (#233) 2024-06-04 11:19:22 -07:00
Josh McFarlin 1e8ea64017 feat: Use uuid v7 in stub database adapter (#234) 2024-06-03 14:14:59 -07:00
Will Schurman af495a9a85 feat: add previousValue to privacy policy context for updates (#232) 2024-05-31 13:14:32 -07:00
Will Schurman 60fc9a4c4b feat: make canViewerDeleteAsync recursive (#224) 2024-05-31 12:41:36 -07:00
Will Schurman 14bd959e53 chore: Update README.md 2024-05-30 10:17:03 -07:00
Will Schurman 91d46ba49f chore: add test coverage for remaining code (#231) 2024-05-30 10:11:35 -07:00
Will Schurman 16e7c21b1c chore: add more tests for stub database adapter and test utils (#230) 2024-05-30 10:11:02 -07:00
Will Schurman 3ce0ac91f8 chore: add more tests for entity loader (#229) 2024-05-30 10:10:29 -07:00
Will Schurman 428cc6fc5c chore: add more tests for privacy policy rules and nested transaction query contexts (#228) 2024-05-30 10:09:57 -07:00
Will Schurman 8490ad5a19 chore: add more test coverage to postgres transaction isolation levels (#227) 2024-05-30 10:09:16 -07:00
Will Schurman 8a448b5c42 chore: Update README.md 2024-05-28 12:19:23 -07:00
Will Schurman 3fd8a92cc8 chore: Update README.md 2024-05-28 12:18:37 -07:00
Will Schurman b5998063a6 chore: set codecov default branch (#226) 2024-05-28 12:12:15 -07:00
Will Schurman 8fc0849618 chore: ignore entity-example package from codecov (#225) 2024-05-28 12:01:19 -07:00
Will Schurman 87532525d0 feat: Add StrictEnumField with better validation (#222) 2024-05-02 11:30:32 -07:00
Will Schurman 33771295c5 v0.35.0 2024-04-11 09:55:15 -07:00
Will Schurman 1bdfbd6b56 fix: Revert: require explicit query context specification (#219)
This reverts commit 8b0b31fdde.
2024-04-11 09:51:40 -07:00
Will Schurman 0fa6f554a2 v0.34.0 2024-04-11 09:13:48 -07:00
Will Schurman 8b0b31fdde feat: require explicit query context specification (#219) 2024-04-11 09:09:18 -07:00
James Ide cc6f3dc1e6 feat: Allow UUIDv6/7/8 in UUIDField validation (#221)
Why
---
Currently the UUIDField type supports only UUIDv1-5 and to use a newer UUID version we need to use StringField.

How
---
Manually implemented UUID validation, adding support for versions 6, 7, and 8. Also added support for the "max" UUID, which is now part of the spec.

Test Plan
---
Added a UUIDv7 value (from https://uuid7.com/) to test cases.
2024-04-08 18:17:24 -07:00
Will Schurman 9c43a56a39 BREAKING CHANGE: hoist the responsibility of sharing local memory cache adapter into application (#218) 2023-12-06 12:38:30 -08:00
Will Schurman ef7b452c9f docs: fix EntityMutationTrigger docblocks (#216) 2023-12-04 16:18:29 -08:00
Will Schurman 659e08386f v0.33.0 2023-10-06 16:00:57 -07:00
Will Schurman aad94aca47 feat: add loadManyByIDsNullableAsync loader method (#214) 2023-10-04 15:28:28 -07:00
Will Schurman d2a4405fa1 v0.32.0 2023-06-09 20:13:23 -05:00
Will Schurman 503a2fb6a8 fix: remove unused field from redis configuration (#213) 2023-06-09 17:08:57 -07:00
Will Schurman 3a00a43036 chore: fix lint (#212) 2023-06-09 11:38:07 -07:00
Will Schurman 05b14843a6 chore: upgrade dependencies (#211) 2023-06-09 11:33:58 -07:00
Will Schurman 6f8020fbf8 chore: refactor common cache logic into GenericEntityCacheAdapter (#209) 2023-06-09 11:32:34 -07:00
Will Schurman 4b18010d42 feat: entity companion definition static thunk (#210) 2023-06-09 11:20:24 -07:00
Will Schurman 2069a0d9fd feat: add ability to specify knex transaction config (#207) 2023-04-20 18:10:07 -07:00
James Ide 83fe9cdea5 v0.31.1 2023-03-06 22:08:09 -08:00
James Ide 1934216b70 [rfc] loadFirstByFieldEqualityConjunction, a convenience method (#206) 2023-01-24 16:41:06 -08:00
Will Schurman 92f5529eba v0.31.0 2023-01-18 21:51:04 -08:00
Will Schurman 67732d6847 chore: upgrade ioredis to 5.2.5 (#205) 2023-01-18 21:49:52 -08:00
Will Schurman bb0cd51eb5 chore: upgrade knex to 2.4.1 for CVE-2016-20018 (#204) 2023-01-18 14:02:47 -08:00
Will Schurman abd86b91d3 v0.30.0 2022-12-23 15:42:23 -07:00
Will Schurman b5f734b1e1 [www] Upgrade knex to 2.3.0 (#203) 2022-12-23 12:13:46 -08:00
Will Schurman b11bd24351 v0.29.0 2022-10-06 11:48:07 -07:00
Will Schurman 684f91b1f2 feat: add example of redis mget batching using @expo/batcher (#199) 2022-10-06 11:42:22 -07:00
Will Schurman e381f5b2ac feat: change redis cache adapter to use redis-like interface (#200) 2022-10-06 11:34:28 -07:00
James Ide 69692c3b64 docs: fix typo descripting afterAll trigger (#198)
The docblock for the afterAll trigger used to say that the triggers were run before create/update/delete operations. s/before/after/
2022-10-05 14:53:12 -07:00
407 changed files with 58381 additions and 21893 deletions

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is Entity, a privacy-aware data layer for defining, caching, and authorizing access to application data models.
## Essential Development Commands
### Dependencies
- `yarn add` - Add a dependency. Run this only if necessary and ask before running.
### Building and Linting
- `yarn tsc` - Typecheck the code
- `yarn lint` - Run oxlint on source code
- `yarn format` - Format code with oxfmt
- `yarn format-check` - Check formatting without writing
- `yarn ctix` - Build barrel index.ts files
### Testing
- `yarn test` - Run unit tests
- `yarn integration` - Run integration tests against dockerized environment
### Commit messages
- All PRs use conventional commits for their titles: https://www.conventionalcommits.org/
## Code conventions
- Backwards compatibility is a non-goal of refactorings of this library. Consumers rely upon typescript to know what needs to be migrated, so all breaking changes should be detectable via running typescript post-entity-version-upgrade in a consumer application that makes use of this package.
- Do not use jest's `test.skip`
- Prefer not to use dynamic imports for now
- Whenever technically correct, use typescript types for something instead of `any`
- Prefer American English spellings of words over British English. For example, prefer "Adapter" over "Adaptor".
+54
View File
@@ -0,0 +1,54 @@
{
"mode": "bundle",
"fileExt": "keep",
"overwrite": true,
"backup": false,
"generationStyle": "default-alias-named-star",
"options": [
{
"project": "packages/entity/tsconfig.json",
"output": "packages/entity/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-cache-adapter-local-memory/tsconfig.json",
"output": "packages/entity-cache-adapter-local-memory/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-cache-adapter-redis/tsconfig.json",
"output": "packages/entity-cache-adapter-redis/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-database-adapter-knex/tsconfig.json",
"output": "packages/entity-database-adapter-knex/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-ip-address-field/tsconfig.json",
"output": "packages/entity-ip-address-field/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-secondary-cache-local-memory/tsconfig.json",
"output": "packages/entity-secondary-cache-local-memory/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-secondary-cache-redis/tsconfig.json",
"output": "packages/entity-secondary-cache-redis/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-testing-utils/tsconfig.json",
"output": "packages/entity-testing-utils/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
},
{
"project": "packages/entity-database-adapter-knex-testing-utils/tsconfig.json",
"output": "packages/entity-database-adapter-knex-testing-utils/src",
"exclude": [ "**/__testfixtures__/**", "**/__tests__/**" ]
}
]
}
-55
View File
@@ -1,55 +0,0 @@
{
"root": true,
"extends": [
"universe/node",
"universe/shared/typescript-analysis"
],
"plugins": [
"eslint-plugin-tsdoc"
],
"rules": {
"no-restricted-imports": ["error",{ "paths": [".", "..", "../.."] }],
"tsdoc/syntax": "warn",
"no-console": "warn",
"handle-callback-err": "off"
},
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.d.ts"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"@typescript-eslint/naming-convention": [
"warn",
{
"selector": "typeLike",
"format": [
"PascalCase"
]
},
{
"selector": "enumMember",
"format": [
"UPPER_CASE"
]
}
],
"no-dupe-class-members": "off",
"@typescript-eslint/no-dupe-class-members": [
"error"
]
}
}
]
}
+1
View File
@@ -0,0 +1 @@
packages/entity-codemod/**/__testfixtures__/**
+1
View File
@@ -0,0 +1 @@
yarn.lock -diff linguist-generated
-27
View File
@@ -1,27 +0,0 @@
name: docs
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Cache Node.js modules
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/node_modules
key: ${{ runner.OS }}-node_modules-${{ hashFiles('yarn.lock') }}
- run: yarn --frozen-lockfile
- run: yarn typedoc
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc
+20
View File
@@ -0,0 +1,20 @@
name: Lint PR
on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+45 -29
View File
@@ -2,37 +2,53 @@ name: tests
on:
push:
branches: [ main ]
branches: [main]
pull_request:
env:
# renovate: datasource=github-releases depName=jdx/mise
MISE_VERSION: v2026.10.19
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v1
with:
node-version: 18.x
- name: Cache Node.js modules
uses: actions/cache@v1
with:
path: ${{ github.workspace }}/node_modules
key: ${{ runner.OS }}-node_modules-${{ hashFiles('yarn.lock') }}
- run: yarn --frozen-lockfile
- run: yarn lint -- -- --max-warnings=0
- run: yarn test -- -- --coverage
- run: yarn integration -- -- --coverage
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./packages/*/coverage/clover.xml
flags: unittest
fail_ci_if_error: true
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./packages/*/coverage-integration/clover.xml
flags: integration
fail_ci_if_error: true
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.MISE_VERSION }}
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
cache: yarn
- run: mise run ci
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage
flags: unittest
fail_ci_if_error: true
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage-integration
flags: integration
fail_ci_if_error: true
docs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ env.MISE_VERSION }}
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
cache: yarn
- run: mise run typedoc
- name: Deploy
if: ${{ github.ref == 'refs/heads/main' }}
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc
+13 -1
View File
@@ -23,12 +23,24 @@ node_modules/
.yarn-integrity
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*
# [yarn](https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored)
**/.yarn/*
!**/.yarn/cache
!**/.yarn/patches
!**/.yarn/plugins
!**/.yarn/releases
!**/.yarn/sdks
!**/.yarn/versions
**/.pnp.*
.nx/cache
.nx/workspace-data
# Entity-specific ignores
build/
+17
View File
@@ -0,0 +1,17 @@
{
"printWidth": 100,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"sortImports": {
"groups": [
["builtin", "external"],
["internal", "subpath"],
["parent", "sibling", "index"],
"unknown"
],
"ignoreCase": false
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"jsxBracketSameLine": true
}
+11 -2
View File
@@ -1,3 +1,12 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
"typescript.tsdk": "node_modules/typescript/lib",
"oxc.fmt.configPath": ".oxfmtrc.json",
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode"
}
}
+7
View File
@@ -0,0 +1,7 @@
nodeLinker: node-modules
npmMinimalAgeGate: 2d
npmPreapprovedPackages:
- '@expo/*'
- oxlint-config-universe
+442 -256
View File
@@ -3,469 +3,655 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
### Features
* add BigIntField ([#193](https://github.com/expo/entity/issues/193)) ([acbad82](https://github.com/expo/entity/commit/acbad826a37bb5a054b4dcf9be91d11aa5f7f29a))
* nested transactions ([#194](https://github.com/expo/entity/issues/194)) ([a77b914](https://github.com/expo/entity/commit/a77b914ad6dc018d1e309c9caf4971088a553ecf))
- add notAuthorizedEntity to getUserFacingDenialReason ([#732](https://github.com/expo/entity/issues/732)) ([24109be](https://github.com/expo/entity/commit/24109bec32c03b228bd35cef8bd6a379dcf7277b))
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
### Bug Fixes
- declare runtime dependencies imported by published packages ([#717](https://github.com/expo/entity/issues/717)) ([3c96f3b](https://github.com/expo/entity/commit/3c96f3b9ea2b097c979447354f0abe35ed5e8d57))
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
### Features
- add transient error for PostgreSQL lock timeouts ([#716](https://github.com/expo/entity/issues/716)) ([13ce74e](https://github.com/expo/entity/commit/13ce74e93aec0a0b0b84edd1e1e89d785a3dbf46)), closes [#714](https://github.com/expo/entity/issues/714) [#715](https://github.com/expo/entity/issues/715)
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
### Features
- add ID-based and field-based knex database loaders for use with locking modifiers ([#706](https://github.com/expo/entity/issues/706)) ([e87e2a3](https://github.com/expo/entity/commit/e87e2a3a28f4279f5911bfd0189964b94a2ced7f))
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
### Features
- add forShare and skipLocked selection modifiers to knex loader ([#705](https://github.com/expo/entity/issues/705)) ([243a038](https://github.com/expo/entity/commit/243a0386fb9755f3926ade8dcc1b92adf38cd923))
- add forUpdate to knex loader ([#704](https://github.com/expo/entity/issues/704)) ([727f166](https://github.com/expo/entity/commit/727f16662d8baa3757bb8e94f51d837f17fd333d))
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package root
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package root
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
### Bug Fixes
- **deps:** update dependency koa to v3.2.1 ([#634](https://github.com/expo/entity/issues/634)) ([1c17dc0](https://github.com/expo/entity/commit/1c17dc0f37ec68fb73aae1f92b3ce2c79062b93e))
### Features
- accept array of parent entity classes in cascade deletion rule ([#650](https://github.com/expo/entity/issues/650)) ([df75910](https://github.com/expo/entity/commit/df75910cbdc89df60ed2f23dbedf8862ed5c9077))
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
### Bug Fixes
- only throw EntityDatabaseAdapterPaginationCursorInvalidError from cursor decoding logic ([#537](https://github.com/expo/entity/issues/537)) ([e3cee07](https://github.com/expo/entity/commit/e3cee07091af8d1e6c5bb2d82f13e837c7658ea9))
- remove unused arg from EntityInvalidationUtils ([#536](https://github.com/expo/entity/issues/536)) ([0a1d1b5](https://github.com/expo/entity/commit/0a1d1b5972c15c932f5c31015f2519163f882f83))
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
### Bug Fixes
- **deps:** update dependency graphql to v16.13.1 ([#510](https://github.com/expo/entity/issues/510)) ([1a8535f](https://github.com/expo/entity/commit/1a8535f983f38aaa462a8d587e2f62f5fab1043c))
### Features
- update the SQLExpression and SQLFragmentHelpers APIs ([#516](https://github.com/expo/entity/issues/516)) ([112d4dc](https://github.com/expo/entity/commit/112d4dc9b379da5317ff6acaf1d2e9bd597128a6))
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
### Features
- Add SQLExpression class that supports fluent comparison method API ([#506](https://github.com/expo/entity/issues/506)) ([535b681](https://github.com/expo/entity/commit/535b6811e08578aba1aadb095f3e401cfe7eaa26))
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
### Bug Fixes
- **deps:** update dependency @koa/router to v15.3.1 ([#497](https://github.com/expo/entity/issues/497)) ([72b3c80](https://github.com/expo/entity/commit/72b3c80d12a1afa3c5593bcf6d7ad233c3524341))
### Features
- add support to SQLFragment for `= ANY(?)` ([#498](https://github.com/expo/entity/issues/498)) ([b931daa](https://github.com/expo/entity/commit/b931daa9a082cdb33f4559d6d32e1655f0459b79))
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
### Bug Fixes
- escape all matching portions of parts of cacher cache keys ([#486](https://github.com/expo/entity/issues/486)) ([21f2f6f](https://github.com/expo/entity/commit/21f2f6f461ee771e8b1c1b22dbaf9b275094c0db))
- fix some security review suggestions ([#485](https://github.com/expo/entity/issues/485)) ([e263bd2](https://github.com/expo/entity/commit/e263bd218cda579365766d0918c4961304bd1d9f))
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package root
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
### Bug Fixes
- Add default for TSelectedFields for application-instantiated types ([#438](https://github.com/expo/entity/issues/438)) ([3d4aa0e](https://github.com/expo/entity/commit/3d4aa0e9efa519a36f3db69bd8f117a993c100fa))
- Apply same viewer context subclass type mechanism to knexLoader and related methods ([#472](https://github.com/expo/entity/issues/472)) ([018a40b](https://github.com/expo/entity/commit/018a40bc01d579ffbf88f308e641921e6fc39097))
- correct discriminated union type for forward/backward pagination ([#474](https://github.com/expo/entity/issues/474)) ([90ae6e2](https://github.com/expo/entity/commit/90ae6e299bda26ac9a80d83e83334d4152c022dd))
- correct pagination behavior for standard pagination with descending ordering clauses ([#477](https://github.com/expo/entity/issues/477)) ([e32c4c2](https://github.com/expo/entity/commit/e32c4c2bcc05f225a556f95c90f1ee0f4e24de74))
- correct precedence parentheses for SQLFragment joins ([#476](https://github.com/expo/entity/issues/476)) ([2c4c649](https://github.com/expo/entity/commit/2c4c649a4bca5bc9ffdc41eb3135b2dfe4db5e57)), closes [/github.com/expo/entity/blob/main/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts#L303](https://github.com//github.com/expo/entity/blob/main/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts/issues/L303)
- **deps:** update dependency @apollo/server to v5.4.0 [security] ([#420](https://github.com/expo/entity/issues/420)) ([cd81c5a](https://github.com/expo/entity/commit/cd81c5a5b6be43ef60248eeea4031a883dd1eae8))
- **deps:** update dependency @koa/bodyparser to v6.1.0 ([#405](https://github.com/expo/entity/issues/405)) ([2dda22d](https://github.com/expo/entity/commit/2dda22d761f87851e3fcccc8443597daf8ac42e8))
- **deps:** update dependency @koa/router to v15.3.0 ([#406](https://github.com/expo/entity/issues/406)) ([9e88cf6](https://github.com/expo/entity/commit/9e88cf6aab76902c65994d28de8dab73f1267021))
- **deps:** update dependency graphql to v16.13.0 ([#479](https://github.com/expo/entity/issues/479)) ([976e2c3](https://github.com/expo/entity/commit/976e2c33867da71a2920d050c4086e2af8f6d54d))
- **deps:** update dependency koa to v3.1.2 [security] ([#484](https://github.com/expo/entity/issues/484)) ([b258dad](https://github.com/expo/entity/commit/b258dad2ed69c473f04d57d26910eab2032d142c))
- eliminate some unnecessary any casts ([#448](https://github.com/expo/entity/issues/448)) ([cc78029](https://github.com/expo/entity/commit/cc78029117c14510b0934b5e0d19876a45570f8d))
- explicitly document behavior when cursor entity no longer exists ([#453](https://github.com/expo/entity/issues/453)) ([4dc156c](https://github.com/expo/entity/commit/4dc156c7a8dc50bc9e2802be133b9e258b289f54)), closes [#422](https://github.com/expo/entity/issues/422) [#431](https://github.com/expo/entity/issues/431)
- move EntityPrivacyUtils back into core package ([#429](https://github.com/expo/entity/issues/429)) ([56ec27d](https://github.com/expo/entity/commit/56ec27d5f201d83930a2cc05dfc380a054bff1e1))
- pass in augmentable classes to installation ([#437](https://github.com/expo/entity/issues/437)) ([f5c728b](https://github.com/expo/entity/commit/f5c728be565e01c0a8ace9ef51f6de8f49184dab)), closes [/github.com/expo/entity/pull/410#pullrequestreview-3765893898](https://github.com//github.com/expo/entity/pull/410/issues/pullrequestreview-3765893898)
- refactor install method to instead be free functions ([#441](https://github.com/expo/entity/issues/441)) ([51d632d](https://github.com/expo/entity/commit/51d632da1619dc039d5c41ce6533f63254ec4337)), closes [#410](https://github.com/expo/entity/issues/410)
- Reorganize constructionUtils to make non-public ([#454](https://github.com/expo/entity/issues/454)) ([d9377a2](https://github.com/expo/entity/commit/d9377a290b7e0c6803f9e83537eddb7204bc9320)), closes [#410](https://github.com/expo/entity/issues/410)
- revert change to codemod now that Entity.knexLoader is supported again via subclass ([#443](https://github.com/expo/entity/issues/443)) ([ed6b2e0](https://github.com/expo/entity/commit/ed6b2e05539c77ee2abd2c6da74e2a451967d06e)), closes [#441](https://github.com/expo/entity/issues/441)
- Type idField in EntityConfiguration as TIDField ([#425](https://github.com/expo/entity/issues/425)) ([be40bcf](https://github.com/expo/entity/commit/be40bcffa6e7db9b7ad75e1a80bbb48ff417c6c5))
- update integration test to use BlahEntity.knexLoader syntax ([#444](https://github.com/expo/entity/issues/444)) ([457f58a](https://github.com/expo/entity/commit/457f58ad2c4e1cf09f3132f3b9af5008ba0290c7)), closes [#441](https://github.com/expo/entity/issues/441)
- Use TSelectedFields for knex loader order by method ([#424](https://github.com/expo/entity/issues/424)) ([a9d09f4](https://github.com/expo/entity/commit/a9d09f42b67378d06133bcfb5b99c9afb17aac0d))
### Features
- Add authorization action to EntityPrivacyPolicyRuleEvaluationContext ([#464](https://github.com/expo/entity/issues/464)) ([e71ae5f](https://github.com/expo/entity/commit/e71ae5fcbae95096e0d6a42a8c960896ab83ccfb))
- add codemod for upcoming v0.55.0-v0.56.0 ([#413](https://github.com/expo/entity/issues/413)) ([aba0ce5](https://github.com/expo/entity/commit/aba0ce5b5f38cb7b01956cde14a4714e2a03abb4)), closes [#407](https://github.com/expo/entity/issues/407) [-#410](https://github.com/-/issues/410)
- add entity-database-adapter-knex-testing-utils containing StubPostgresDatabaseAdapter ([#412](https://github.com/expo/entity/issues/412)) ([eeccf59](https://github.com/expo/entity/commit/eeccf594c11bf090aa83bbf71fb4ead8668aec7d))
- add entityField SQL helper ([#481](https://github.com/expo/entity/issues/481)) ([459a5ff](https://github.com/expo/entity/commit/459a5ff85014a8c7396c0856dffacb1491ccb963))
- Add ilike and trigram similarity search to pagination ([#431](https://github.com/expo/entity/issues/431)) ([f72cbca](https://github.com/expo/entity/commit/f72cbcaf5a722e6bb8299644ec32b07f1d3362a9)), closes [#422](https://github.com/expo/entity/issues/422)
- add method to get pagination cursor for single entity ([#475](https://github.com/expo/entity/issues/475)) ([898f71c](https://github.com/expo/entity/commit/898f71ce082c2593585575b60bd6ef2546717124))
- Add paginated loader to entity-database-adapter-knex ([#422](https://github.com/expo/entity/issues/422)) ([58deffd](https://github.com/expo/entity/commit/58deffd426b0214073ede608cd47c04db2596d60))
- Add pagination max page size configuration ([#436](https://github.com/expo/entity/issues/436)) ([3ec4b5f](https://github.com/expo/entity/commit/3ec4b5f0cd53bf18d6bcfa27dd29b70d3f4d5f19))
- add PostgresEntity/ReadonlyPostgresEntity classes that expose knexLoader methods ([#442](https://github.com/expo/entity/issues/442)) ([40b409f](https://github.com/expo/entity/commit/40b409f6f889febf8b90b8837caecac26ff64174)), closes [#441](https://github.com/expo/entity/issues/441)
- Add raw sqlfragment orderBy capability for pagination ([#457](https://github.com/expo/entity/issues/457)) ([0615ffa](https://github.com/expo/entity/commit/0615ffacffbe9e903a9bf03b171c102f0554b992))
- add support for nulls ordering in order by clauses ([#468](https://github.com/expo/entity/issues/468)) ([6c0838d](https://github.com/expo/entity/commit/6c0838dc577a3b6190e3551ee834925d7bd53bc6))
- support nullable search fields with postgres transforms ([#470](https://github.com/expo/entity/issues/470)) ([53b0604](https://github.com/expo/entity/commit/53b0604152b8ca5ad9fc616d18478f93473c7eb0))
# [0.55.0](https://github.com/expo/entity/compare/v0.54.0...v0.55.0) (2026-01-27)
### Bug Fixes
- **deps:** update dependency @apollo/server to v5.3.0 ([#403](https://github.com/expo/entity/issues/403)) ([b758c4d](https://github.com/expo/entity/commit/b758c4d344294797430de0afb88e47f558b6fa8d))
### Features
- add AllowIf[Any|All]SubRulesAllowPrivacyPolicyRule rules ([#401](https://github.com/expo/entity/issues/401)) ([5db8977](https://github.com/expo/entity/commit/5db89777c605eda9a8393227ceeb641e460dc463))
- Add AllowIfInParentCascadeDeletionPrivacyPolicyRule ([#399](https://github.com/expo/entity/issues/399)) ([56ed357](https://github.com/expo/entity/commit/56ed357b923a8374a3c120e3eef5d487d5fb2f9c))
- add EvaluateIfEntityFieldPredicatePrivacyPolicyRule ([#400](https://github.com/expo/entity/issues/400)) ([f3b575c](https://github.com/expo/entity/commit/f3b575cd00d47c747cf45e96c6db7857592b0f93))
# [0.54.0](https://github.com/expo/entity/compare/v0.53.0...v0.54.0) (2026-01-21)
### Bug Fixes
- **deps:** pin dependencies ([#328](https://github.com/expo/entity/issues/328)) ([5679f27](https://github.com/expo/entity/commit/5679f27209f1515ca5e626d858c4aa054b414625))
- **deps:** pin dependency @apollo/server to 4.12.2 ([#330](https://github.com/expo/entity/issues/330)) ([7038912](https://github.com/expo/entity/commit/7038912ca5e5b203ebbca4b46bda5612e7660eac))
- **deps:** pin dependency @koa/bodyparser to 6.0.0 ([#339](https://github.com/expo/entity/issues/339)) ([9f93926](https://github.com/expo/entity/commit/9f939269cf843059ec0ac729d470db074730348e))
- **deps:** pin dependency koa to 2.16.3 ([#331](https://github.com/expo/entity/issues/331)) ([ce55922](https://github.com/expo/entity/commit/ce55922e3ec6692a9ea4847427dc475902174830))
- Inline map().filter() loops in EntityLoaderUtils ([#369](https://github.com/expo/entity/issues/369)) ([ea6c128](https://github.com/expo/entity/commit/ea6c128d9c8fc21484645796f79e1aa8839dd395))
- run tsc during tests CI job ([#373](https://github.com/expo/entity/issues/373)) ([1cc57ed](https://github.com/expo/entity/commit/1cc57eddce72663cff17a5102afa6c3e8248aee4))
### Features
- synthetically set cascading set-null fields to null during canViewerDeleteAsync evaluation ([#376](https://github.com/expo/entity/issues/376)) ([544bb7c](https://github.com/expo/entity/commit/544bb7c3f2f41419cb3b779f7e3d968e09455fa0))
# [0.53.0](https://github.com/expo/entity/compare/v0.52.0...v0.53.0) (2025-12-19)
**Note:** Version bump only for package root
# [0.52.0](https://github.com/expo/entity/compare/v0.51.0...v0.52.0) (2025-12-19)
### Features
- switch local cache to an interface ([#322](https://github.com/expo/entity/issues/322)) ([c453726](https://github.com/expo/entity/commit/c4537262d810ea01e519f9aec6ae5b17a54ce936))
# [0.51.0](https://github.com/expo/entity/compare/v0.50.0...v0.51.0) (2025-11-21)
### Bug Fixes
- add default for TSelectedFields in PrivacyPolicyTestUtils Case ([#316](https://github.com/expo/entity/issues/316)) ([2f9d487](https://github.com/expo/entity/commit/2f9d4873aaefe891920c25a41088f7c467da6113))
# [0.50.0](https://github.com/expo/entity/compare/v0.49.0...v0.50.0) (2025-10-07)
### Bug Fixes
- make in memory and stub database adapters behave more similarly to sql ([#315](https://github.com/expo/entity/issues/315)) ([0f53c24](https://github.com/expo/entity/commit/0f53c24faff8a994906e630d7f17d67d89ff5b0f)), closes [#311](https://github.com/expo/entity/issues/311)
# [0.49.0](https://github.com/expo/entity/compare/v0.48.0...v0.49.0) (2025-10-07)
### Bug Fixes
- add readonly modifier to partitionArray utility method ([#307](https://github.com/expo/entity/issues/307)) ([077d53c](https://github.com/expo/entity/commit/077d53cd86d4c54132ea2ebcc59d1d75e2b507d0))
- validate that UUID field is lower-case ([#310](https://github.com/expo/entity/issues/310)) ([949f00d](https://github.com/expo/entity/commit/949f00d93b52e405188b9b23e23ae59ce1c546de)), closes [/github.com/expo/entity/blob/077d53cd86d4c54132ea2ebcc59d1d75e2b507d0/packages/entity/src/EntityDatabaseAdapter.ts#L182](https://github.com//github.com/expo/entity/blob/077d53cd86d4c54132ea2ebcc59d1d75e2b507d0/packages/entity/src/EntityDatabaseAdapter.ts/issues/L182) [#309](https://github.com/expo/entity/issues/309)
### Features
- add more documentation to entity errors ([#312](https://github.com/expo/entity/issues/312)) ([4c3fd17](https://github.com/expo/entity/commit/4c3fd175a91cf3107b1ac413e39e52e051a9dc4b))
- throw specific errors for database adapter issues ([#311](https://github.com/expo/entity/issues/311)) ([f6639f9](https://github.com/expo/entity/commit/f6639f9def4ac20b628dd48fdca8d7cf61b6347b))
# [0.48.0](https://github.com/expo/entity/compare/v0.47.0...v0.48.0) (2025-09-19)
**Note:** Version bump only for package root
# [0.47.0](https://github.com/expo/entity/compare/v0.46.0...v0.47.0) (2025-09-19)
### Features
- add support for Buffer fields ([#305](https://github.com/expo/entity/issues/305)) ([31b52a5](https://github.com/expo/entity/commit/31b52a5f75842f4810f66c12ad03a10d9192f889))
- allow manual specification of cascadingDeleteCause in delete mutator factory method ([#306](https://github.com/expo/entity/issues/306)) ([4a3eda2](https://github.com/expo/entity/commit/4a3eda209fa747ee093ca3053b3196bf9a52193d))
# [0.46.0](https://github.com/expo/entity/compare/v0.45.0...v0.46.0) (2025-06-26)
**Note:** Version bump only for package root
# [0.45.0](https://github.com/expo/entity/compare/v0.44.0...v0.45.0) (2025-06-06)
### Features
- add ability to define a custom cache key invalidation version supplier ([#290](https://github.com/expo/entity/issues/290)) ([a3ab61b](https://github.com/expo/entity/commit/a3ab61bfa8d2eab3bbd9c703c5e483184bb67f13))
- add createOrGetExistingAsync and createWithUniqueConstraintRecoveryAsync utility methods ([#291](https://github.com/expo/entity/issues/291)) ([78bc264](https://github.com/expo/entity/commit/78bc264b3328692b4b6722388a57d298f02e65c5))
# [0.44.0](https://github.com/expo/entity/compare/v0.43.0...v0.44.0) (2025-05-29)
### Bug Fixes
- introduce transactional dataloader mode and fix global transaction ID generation ([#289](https://github.com/expo/entity/issues/289)) ([d3e89bd](https://github.com/expo/entity/commit/d3e89bdd82e6df5c5b35522bc3574fd9e1cfddff))
### Features
- add transaction-scoped dataloaders ([#284](https://github.com/expo/entity/issues/284)) ([e054a0d](https://github.com/expo/entity/commit/e054a0d74b3a39158ca7d73d05f59d3633113400)), closes [#98](https://github.com/expo/entity/issues/98) [#194](https://github.com/expo/entity/issues/194)
# [0.43.0](https://github.com/expo/entity/compare/v0.42.0...v0.43.0) (2025-04-10)
### Bug Fixes
- fix generated tsc definition sentinel type visibility ([#283](https://github.com/expo/entity/issues/283)) ([9758fd5](https://github.com/expo/entity/commit/9758fd5df8695a8121258cd988c0c79ce8a3e4b6))
# [0.42.0](https://github.com/expo/entity/compare/v0.41.0...v0.42.0) (2025-04-10)
### Bug Fixes
- fix metrics for dataloader waterfall logging ([#267](https://github.com/expo/entity/issues/267)) ([1d95eba](https://github.com/expo/entity/commit/1d95eba1f9efb67164c3affc917c9b2514e1529f))
- move source of truth of loader docblocks to enforcing loader since it is not the default loader ([#264](https://github.com/expo/entity/issues/264)) ([aa56b70](https://github.com/expo/entity/commit/aa56b705db115cf760125208eb2d3585a3794e58))
### Features
- add @expo/entity-testing-utils package ([#280](https://github.com/expo/entity/issues/280)) ([485894a](https://github.com/expo/entity/commit/485894af16e233d533a9480285bbda56812cbe0b))
- add composite field loading and caching ([#272](https://github.com/expo/entity/issues/272)) ([f0aa0da](https://github.com/expo/entity/commit/f0aa0dafebdb56418cbd22dda437233d850db3e7))
- add some utility classes and methods in preparation for composite keys ([#269](https://github.com/expo/entity/issues/269)) ([ad41af1](https://github.com/expo/entity/commit/ad41af1c826fb267f5e463771107c85802578a19)), closes [#201](https://github.com/expo/entity/issues/201)
- change entity id field generic to field name and derive type where necessary ([#278](https://github.com/expo/entity/issues/278)) ([b7e524c](https://github.com/expo/entity/commit/b7e524c41892797608d0b884410b8d520a80a9ef))
- convert batched/cached loader interface to holder pattern ([#271](https://github.com/expo/entity/issues/271)) ([06b3cb7](https://github.com/expo/entity/commit/06b3cb7f8076ef9e5b83cdf154da159801bd14cc)), closes [#201](https://github.com/expo/entity/issues/201)
- enforce explicit id field cache property ([#276](https://github.com/expo/entity/issues/276)) ([1da5cc0](https://github.com/expo/entity/commit/1da5cc01e1affc3b3338a0ab2050504b5853ac6f))
- invalidate n+/-1 cacheKeyVersion for entities for push safety ([#275](https://github.com/expo/entity/issues/275)) ([d9c1852](https://github.com/expo/entity/commit/d9c1852706f98f0761ff24ddd244e4ed2beca580))
# [0.41.0](https://github.com/expo/entity/compare/v0.40.0...v0.41.0) (2025-03-07)
- feat!: Default all loaders and mutators to enforcing (#256) ([a976e76](https://github.com/expo/entity/commit/a976e765098bca27049585e43a3bed5c71b9ac6b)), closes [#256](https://github.com/expo/entity/issues/256)
### Features
- Create codemod for v0.40.0 -> v0.41.0 ([#260](https://github.com/expo/entity/issues/260)) ([4a2b61f](https://github.com/expo/entity/commit/4a2b61f2bd8edfc19109ef4c8ac780eaf0f7795c))
### BREAKING CHANGES
- Default all loaders and mutators to enforcing
# [0.40.0](https://github.com/expo/entity/compare/v0.39.0...v0.40.0) (2025-02-27)
### Features
- Create codemod for v0.39.0 -> v0.40.0 ([#259](https://github.com/expo/entity/issues/259)) ([d63ddd6](https://github.com/expo/entity/commit/d63ddd65997a441a914a71b158a111339970aeab))
### Reverts
- Revert "v0.40.0" ([fa78ce8](https://github.com/expo/entity/commit/fa78ce84e2f2eb8199e36fd51779fc68905001c0))
# [0.39.0](https://github.com/expo/entity/compare/v0.38.0...v0.39.0) (2024-11-21)
### Features
- add function to get authorization results for canViewerUpdateAsync/canViewerDeleteAsync ([#249](https://github.com/expo/entity/issues/249)) ([27a358d](https://github.com/expo/entity/commit/27a358d259503aa8d7d7413bfc1e52e0974dc626))
# [0.38.0](https://github.com/expo/entity/compare/v0.37.0...v0.38.0) (2024-06-27)
### Features
- add EntityEdgeDeletionAuthorizationInferenceBehavior for canViewerDeleteAsync ([#243](https://github.com/expo/entity/issues/243)) ([162b192](https://github.com/expo/entity/commit/162b19257ac720c94fb84d64808fb75748adcdc7))
# [0.37.0](https://github.com/expo/entity/compare/v0.36.0...v0.37.0) (2024-06-12)
### Bug Fixes
- update barrels ([#241](https://github.com/expo/entity/issues/241)) ([8c5f81b](https://github.com/expo/entity/commit/8c5f81bcca107e9b7bbea5f0ab41b0105057806e))
# [0.36.0](https://github.com/expo/entity/compare/v0.35.0...v0.36.0) (2024-06-12)
### Breaking Changes
- make using non-enforcing entity loader explicit ([#238](https://github.com/expo/entity/issues/238)) ([2edc7af](https://github.com/expo/entity/commit/2edc7af7e20c362688ff2ee674fa2245205d6de4))
### Bug Fixes
- always reload entity after update since cascading changes may have changed it since commit ([#233](https://github.com/expo/entity/issues/233)) ([7c3c985](https://github.com/expo/entity/commit/7c3c9854a4dd91d4b73ebdb18bdeadea8b63f4c7))
- constrain entity fields type to string-keyed object ([#235](https://github.com/expo/entity/issues/235)) ([7e2cea1](https://github.com/expo/entity/commit/7e2cea16973a0ae1917f867cd25d6ef7c8eaecef))
- disallow keys of JS Object prototype for safety ([#236](https://github.com/expo/entity/issues/236)) ([05726d4](https://github.com/expo/entity/commit/05726d45fa9b91a6b25ddbc9c7e3e097b02097ca))
### Features
- Add global mutation trigger field to EntityCompanionProvider ([#215](https://github.com/expo/entity/issues/215)) ([6569486](https://github.com/expo/entity/commit/6569486ecddf2fd33c626f634c761e06636b1730))
- add previousValue to privacy policy context for updates ([#232](https://github.com/expo/entity/issues/232)) ([af495a9](https://github.com/expo/entity/commit/af495a9a856a5042f00cfd150348e415139cb85e))
- Add StrictEnumField with better validation ([#222](https://github.com/expo/entity/issues/222)) ([8753252](https://github.com/expo/entity/commit/87532525d090b3a67f1cedd95ac990f7dcb3ab5d))
- make canViewerDeleteAsync recursive ([#224](https://github.com/expo/entity/issues/224)) ([60fc9a4](https://github.com/expo/entity/commit/60fc9a4c4b587b866f2f5a1b3bd22145bc77e6e9))
- Use uuid v7 in stub database adapter ([#234](https://github.com/expo/entity/issues/234)) ([1e8ea64](https://github.com/expo/entity/commit/1e8ea64017430a2ce5ea197678bd78713ad77ecd))
- move entity loader utils into their own object ([#239](https://github.com/expo/entity/issues/239))([93905be](https://github.com/expo/entity/commit/93905be81cf74c5626ca19358794261c17223f95))
# [0.35.0](https://github.com/expo/entity/compare/v0.34.0...v0.35.0) (2024-04-11)
### Bug Fixes
- Revert: require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([1bdfbd6](https://github.com/expo/entity/commit/1bdfbd6b562d1200e4029df0533b5adb2d917831))
# [0.34.0](https://github.com/expo/entity/compare/v0.33.0...v0.34.0) (2024-04-11)
### Features
- Allow UUIDv6/7/8 in UUIDField validation ([#221](https://github.com/expo/entity/issues/221)) ([cc6f3dc](https://github.com/expo/entity/commit/cc6f3dc1e677382eec220723c37b5b883d980372))
- require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([8b0b31f](https://github.com/expo/entity/commit/8b0b31fdde5bd565aa527719003ef283a45f55cc))
# [0.33.0](https://github.com/expo/entity/compare/v0.32.0...v0.33.0) (2023-10-06)
### Features
- add loadManyByIDsNullableAsync loader method ([#214](https://github.com/expo/entity/issues/214)) ([aad94ac](https://github.com/expo/entity/commit/aad94aca479ac07cc7f76b5e1a122ff85a2d1542))
# [0.32.0](https://github.com/expo/entity/compare/v0.31.1...v0.32.0) (2023-06-10)
### Bug Fixes
- remove unused field from redis configuration ([#213](https://github.com/expo/entity/issues/213)) ([503a2fb](https://github.com/expo/entity/commit/503a2fb6a8d57f6a09f6da99daf5ab92fc3e7945))
### Features
- add ability to specify knex transaction config ([#207](https://github.com/expo/entity/issues/207)) ([2069a0d](https://github.com/expo/entity/commit/2069a0d9fd1d8f7d090a71a90a758b2429ea5dd3))
- entity companion definition static thunk ([#210](https://github.com/expo/entity/issues/210)) ([4b18010](https://github.com/expo/entity/commit/4b18010d42be50ef329f428b08330e21bf676586))
# [0.31.1](https://github.com/expo/entity/compare/v0.31.0...v0.31.1) (2023-03-06)
### Features
- add loadFirstByFieldEqualityConjunction, a convenience method ([#206](https://github.com/expo/entity/pull/206)) ([1934216](https://github.com/expo/entity/commit/1934216b70572cf2b9693e0f14939f49b90427dd))
# [0.31.0](https://github.com/expo/entity/compare/v0.30.0...v0.31.0) (2023-01-19)
**Note:** Version bump only for package root
# [0.30.0](https://github.com/expo/entity/compare/v0.29.0...v0.30.0) (2022-12-23)
**Note:** Version bump only for package root
# [0.29.0](https://github.com/expo/entity/compare/v0.28.0...v0.29.0) (2022-10-06)
### Features
- add example of redis mget batching using @expo/batcher ([#199](https://github.com/expo/entity/issues/199)) ([684f91b](https://github.com/expo/entity/commit/684f91b1f270851dbe331df2e2620664eea17266))
- change redis cache adapter to use redis-like interface ([#200](https://github.com/expo/entity/issues/200)) ([e381f5b](https://github.com/expo/entity/commit/e381f5b2acfc17dcade2cf6e8506fa1c1c255e73))
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
### Features
- add BigIntField ([#193](https://github.com/expo/entity/issues/193)) ([acbad82](https://github.com/expo/entity/commit/acbad826a37bb5a054b4dcf9be91d11aa5f7f29a))
- nested transactions ([#194](https://github.com/expo/entity/issues/194)) ([a77b914](https://github.com/expo/entity/commit/a77b914ad6dc018d1e309c9caf4971088a553ecf))
# [0.27.0](https://github.com/expo/entity/compare/v0.26.1...v0.27.0) (2022-08-09)
### Features
* add orderByRaw to loadManyByRawWhereClauseAsync ([#185](https://github.com/expo/entity/issues/185)) ([2817d78](https://github.com/expo/entity/commit/2817d78392dfecd0093111c24d581bdf01dba06d))
- add orderByRaw to loadManyByRawWhereClauseAsync ([#185](https://github.com/expo/entity/issues/185)) ([2817d78](https://github.com/expo/entity/commit/2817d78392dfecd0093111c24d581bdf01dba06d))
## [0.26.1](https://github.com/expo/entity/compare/v0.26.0...v0.26.1) (2022-07-28)
### Features
* recache to higher cache levels when loading from a composed cache ([#182](https://github.com/expo/entity/pull/182))
- recache to higher cache levels when loading from a composed cache ([#182](https://github.com/expo/entity/pull/182))
# [0.26.0](https://github.com/expo/entity/compare/v0.25.3...v0.26.0) (2022-07-14)
### Bug Fixes
* fix workflow branch specification ([#179](https://github.com/expo/entity/issues/179)) ([25d14bf](https://github.com/expo/entity/commit/25d14bf8c1688524ad488aa2b63221393a4f0b05))
- fix workflow branch specification ([#179](https://github.com/expo/entity/issues/179)) ([25d14bf](https://github.com/expo/entity/commit/25d14bf8c1688524ad488aa2b63221393a4f0b05))
### Features
* add EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY ([#178](https://github.com/expo/entity/issues/178)) ([a0c35dd](https://github.com/expo/entity/commit/a0c35ddd7bca05d0c3f41dcf48e16d058918231c))
* update ioredis to v5, make it a devDependency of the cache packages ([#172](https://github.com/expo/entity/issues/172)) ([9fcfbf7](https://github.com/expo/entity/commit/9fcfbf746a61554cefedf7fb0f1a3a9056b58ffe))
- add EntityEdgeDeletionBehavior.SET_NULL_INVALIDATE_CACHE_ONLY ([#178](https://github.com/expo/entity/issues/178)) ([a0c35dd](https://github.com/expo/entity/commit/a0c35ddd7bca05d0c3f41dcf48e16d058918231c))
- update ioredis to v5, make it a devDependency of the cache packages ([#172](https://github.com/expo/entity/issues/172)) ([9fcfbf7](https://github.com/expo/entity/commit/9fcfbf746a61554cefedf7fb0f1a3a9056b58ffe))
## [0.25.3](https://github.com/expo/entity/compare/v0.25.2...v0.25.3) (2022-03-12)
### Bug Fixes
* lock lru-cache to v6.0.0 ([#170](https://github.com/expo/entity/issues/170)) ([293868b](https://github.com/expo/entity/commit/293868b1a73df9f749216af68b1ae94ac22ac2b9))
- lock lru-cache to v6.0.0 ([#170](https://github.com/expo/entity/issues/170)) ([293868b](https://github.com/expo/entity/commit/293868b1a73df9f749216af68b1ae94ac22ac2b9))
## [0.25.2](https://github.com/expo/entity/compare/v0.25.1...v0.25.2) (2022-03-12)
### Bug Fixes
* lru-cache yarn workspace version resolution ([#168](https://github.com/expo/entity/issues/168)) ([e76ca8c](https://github.com/expo/entity/commit/e76ca8c2f72da84aa372a64ef1898ade220ed0a8))
- lru-cache yarn workspace version resolution ([#168](https://github.com/expo/entity/issues/168)) ([e76ca8c](https://github.com/expo/entity/commit/e76ca8c2f72da84aa372a64ef1898ade220ed0a8))
## [0.25.1](https://github.com/expo/entity/compare/v0.25.0...v0.25.1) (2022-03-10)
### Bug Fixes
* add composed cache to index ([#166](https://github.com/expo/entity/issues/166)) ([5c82edc](https://github.com/expo/entity/commit/5c82edcd9f26854878d29950a05852e117bbd723))
- add composed cache to index ([#166](https://github.com/expo/entity/issues/166)) ([5c82edc](https://github.com/expo/entity/commit/5c82edcd9f26854878d29950a05852e117bbd723))
# [0.25.0](https://github.com/expo/entity/compare/v0.24.0...v0.25.0) (2022-03-10)
### Features
* composed cache adapter, composed secondary cache, local memory secondary cache ([#164](https://github.com/expo/entity/issues/164)) ([6b6e70e](https://github.com/expo/entity/commit/6b6e70e75f7e42a4dce13edeb406a4d88d9264f0))
* privacy policy evaluation context ([#162](https://github.com/expo/entity/issues/162)) ([3a81a4c](https://github.com/expo/entity/commit/3a81a4c059e2e995eb800e35e76b3d432c22b23f))
- composed cache adapter, composed secondary cache, local memory secondary cache ([#164](https://github.com/expo/entity/issues/164)) ([6b6e70e](https://github.com/expo/entity/commit/6b6e70e75f7e42a4dce13edeb406a4d88d9264f0))
- privacy policy evaluation context ([#162](https://github.com/expo/entity/issues/162)) ([3a81a4c](https://github.com/expo/entity/commit/3a81a4c059e2e995eb800e35e76b3d432c22b23f))
# [0.24.0](https://github.com/expo/entity/compare/v0.23.0...v0.24.0) (2022-02-16)
### Features
* add local memory cache package ([#158](https://github.com/expo/entity/issues/158)) ([85d3575](https://github.com/expo/entity/commit/85d35752fa0919a62efb776a762a6829e83ca7af))
- add local memory cache package ([#158](https://github.com/expo/entity/issues/158)) ([85d3575](https://github.com/expo/entity/commit/85d35752fa0919a62efb776a762a6829e83ca7af))
### Chores
* Refactor cachers to use an abstract class and interface ([#157](https://github.com/expo/entity/pull/157))
- Refactor cachers to use an abstract class and interface ([#157](https://github.com/expo/entity/pull/157))
# [0.23.0](https://github.com/expo/entity/compare/v0.22.0...v0.23.0) (2022-02-09)
### Breaking changes
* Move data transforming responsibilities to the CacheAdapter instead of the ReadThroughEntityCache. Affects any classes extending the EntityCacheAdapter and relying on the transformation behavior of the ReadThroughEntityCache. ([#153](https://github.com/expo/entity/pull/153))
- Move data transforming responsibilities to the CacheAdapter instead of the ReadThroughEntityCache. Affects any classes extending the EntityCacheAdapter and relying on the transformation behavior of the ReadThroughEntityCache. ([#153](https://github.com/expo/entity/pull/153))
# [0.22.0](https://github.com/expo/entity/compare/v0.21.0...v0.22.0) (2022-02-04)
### Features
* Upgrade knex to 1.x in `@expo/entity-database-adapter-knex` ([#150](https://github.com/expo/entity/issues/150)) ([e1b0ee5](https://github.com/expo/entity/commit/e1b0ee528175646239d88aa71ffce73983eeb52f))
- Upgrade knex to 1.x in `@expo/entity-database-adapter-knex` ([#150](https://github.com/expo/entity/issues/150)) ([e1b0ee5](https://github.com/expo/entity/commit/e1b0ee528175646239d88aa71ffce73983eeb52f))
# [0.21.0](https://github.com/expo/entity/compare/v0.20.0...v0.21.0) (2022-01-03)
### Features
* add pre-commit callbacks on EntityQueryContext ([#147](https://github.com/expo/entity/issues/147)) ([f1d9847](https://github.com/expo/entity/commit/f1d9847210f5775de964509e44ce493e44482b21))
- add pre-commit callbacks on EntityQueryContext ([#147](https://github.com/expo/entity/issues/147)) ([f1d9847](https://github.com/expo/entity/commit/f1d9847210f5775de964509e44ce493e44482b21))
# [0.20.0](https://github.com/expo/entity/compare/v0.19.0...v0.20.0) (2021-12-29)
### Bug Fixes
* separate post-commit invalidation callbacks from trigger and user callbacks ([#144](https://github.com/expo/entity/issues/144)) ([efa131e](https://github.com/expo/entity/commit/efa131ef29532accf0e51b6d867ffe15a27b4c90))
- separate post-commit invalidation callbacks from trigger and user callbacks ([#144](https://github.com/expo/entity/issues/144)) ([efa131e](https://github.com/expo/entity/commit/efa131ef29532accf0e51b6d867ffe15a27b4c90))
### Features
* add cascading deletion info ([#145](https://github.com/expo/entity/issues/145)) ([3727191](https://github.com/expo/entity/commit/372719176742d3ead6b7cbca29cb66b09b3fe09c))
- add cascading deletion info ([#145](https://github.com/expo/entity/issues/145)) ([3727191](https://github.com/expo/entity/commit/372719176742d3ead6b7cbca29cb66b09b3fe09c))
# [0.19.0](https://github.com/expo/entity/compare/v0.18.0...v0.19.0) (2021-10-28)
### Features
* add authorization metrics ([#142](https://github.com/expo/entity/issues/142)) ([7cf54d8](https://github.com/expo/entity/commit/7cf54d83c52e104159a3e2e675cdda2d29afe204))
* add IP support ([#138](https://github.com/expo/entity/issues/138)) ([1405a91](https://github.com/expo/entity/commit/1405a9126ee3a56c3e2a38bf3b697283744f5b5d))
- add authorization metrics ([#142](https://github.com/expo/entity/issues/142)) ([7cf54d8](https://github.com/expo/entity/commit/7cf54d83c52e104159a3e2e675cdda2d29afe204))
- add IP support ([#138](https://github.com/expo/entity/issues/138)) ([1405a91](https://github.com/expo/entity/commit/1405a9126ee3a56c3e2a38bf3b697283744f5b5d))
# [0.18.0](https://github.com/expo/entity/compare/v0.17.0...v0.18.0) (2021-10-14)
### Features
* enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
* expose describeFieldTestCase for use in other packages ([#139](https://github.com/expo/entity/issues/139)) ([1220477](https://github.com/expo/entity/commit/1220477aa331349b482ad015b44f76fa2bda7b90))
* upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
* use better QueryContext types for transactional triggers and validators ([#136](https://github.com/expo/entity/issues/136)) ([5c7efe1](https://github.com/expo/entity/commit/5c7efe1678424ff907687e9327175bdc2538c3e0))
- enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
- expose describeFieldTestCase for use in other packages ([#139](https://github.com/expo/entity/issues/139)) ([1220477](https://github.com/expo/entity/commit/1220477aa331349b482ad015b44f76fa2bda7b90))
- upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
- use better QueryContext types for transactional triggers and validators ([#136](https://github.com/expo/entity/issues/136)) ([5c7efe1](https://github.com/expo/entity/commit/5c7efe1678424ff907687e9327175bdc2538c3e0))
# [0.17.0](https://github.com/expo/entity/compare/v0.16.0...v0.17.0) (2021-08-16)
### Features
* Add IntField and FloatField, deprecate NumberField ([#131](https://github.com/expo/entity/issues/131)) ([2f2d963](https://github.com/expo/entity/commit/2f2d963cf92c6575d78f8eeca344a4aadcc9b8d9))
- Add IntField and FloatField, deprecate NumberField ([#131](https://github.com/expo/entity/issues/131)) ([2f2d963](https://github.com/expo/entity/commit/2f2d963cf92c6575d78f8eeca344a4aadcc9b8d9))
# [0.16.0](https://github.com/expo/entity/compare/v0.15.0...v0.16.0) (2021-07-07)
### Features
* allow null field values in loadManyByFieldEqualityConjunctionAsync ([#130](https://github.com/expo/entity/issues/130)) ([2f37dc6](https://github.com/expo/entity/commit/2f37dc6a37d368b12b3c8375e04f0777fd448797))
- allow null field values in loadManyByFieldEqualityConjunctionAsync ([#130](https://github.com/expo/entity/issues/130)) ([2f37dc6](https://github.com/expo/entity/commit/2f37dc6a37d368b12b3c8375e04f0777fd448797))
# [0.15.0](https://github.com/expo/entity/compare/v0.14.1...v0.15.0) (2021-05-26)
### Bug Fixes
* upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
* upgrade some other packages to fix dependabot alerts ([#126](https://github.com/expo/entity/issues/126)) ([226bffe](https://github.com/expo/entity/commit/226bffe0a95d7b8a3e9e9d46aebdb0edf1eeec1a))
* use column name instead of field name for redis cache key ([#124](https://github.com/expo/entity/issues/124)) ([d78f452](https://github.com/expo/entity/commit/d78f452bb3ac5527069813a03f5b2264375bd8ab))
- upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
- upgrade some other packages to fix dependabot alerts ([#126](https://github.com/expo/entity/issues/126)) ([226bffe](https://github.com/expo/entity/commit/226bffe0a95d7b8a3e9e9d46aebdb0edf1eeec1a))
- use column name instead of field name for redis cache key ([#124](https://github.com/expo/entity/issues/124)) ([d78f452](https://github.com/expo/entity/commit/d78f452bb3ac5527069813a03f5b2264375bd8ab))
### Features
* secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
- secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
## [0.14.1](https://github.com/expo/entity/compare/v0.14.0...v0.14.1) (2021-03-24)
### Bug Fixes
* typescript typedef of EntityMutationInfo ([#121](https://github.com/expo/entity/issues/121)) ([8e8f0dc](https://github.com/expo/entity/commit/8e8f0dc8636e343e29a6012d155e4111477a8989))
- typescript typedef of EntityMutationInfo ([#121](https://github.com/expo/entity/issues/121)) ([8e8f0dc](https://github.com/expo/entity/commit/8e8f0dc8636e343e29a6012d155e4111477a8989))
# [0.14.0](https://github.com/expo/entity/compare/v0.13.0...v0.14.0) (2021-03-24)
### Bug Fixes
* mutation validator and trigger cyclic import structure ([#118](https://github.com/expo/entity/issues/118)) ([b11dbc0](https://github.com/expo/entity/commit/b11dbc087ba5ad13c748032698dd73279b7960b0))
- mutation validator and trigger cyclic import structure ([#118](https://github.com/expo/entity/issues/118)) ([b11dbc0](https://github.com/expo/entity/commit/b11dbc087ba5ad13c748032698dd73279b7960b0))
### Features
* add mutation type to validator and trigger execute functions ([#119](https://github.com/expo/entity/issues/119)) ([ef40fa5](https://github.com/expo/entity/commit/ef40fa5e9cdf53e25e78a9423c698e6d1c9737af))
- add mutation type to validator and trigger execute functions ([#119](https://github.com/expo/entity/issues/119)) ([ef40fa5](https://github.com/expo/entity/commit/ef40fa5e9cdf53e25e78a9423c698e6d1c9737af))
# [0.13.0](https://github.com/expo/entity/compare/v0.12.0...v0.13.0) (2021-02-12)
### Bug Fixes
* allow undefined value for all fields ([#114](https://github.com/expo/entity/issues/114)) ([022e0e9](https://github.com/expo/entity/commit/022e0e9b88a5e84e0c1bc6c8810c850beb05f0cf))
- allow undefined value for all fields ([#114](https://github.com/expo/entity/issues/114)) ([022e0e9](https://github.com/expo/entity/commit/022e0e9b88a5e84e0c1bc6c8810c850beb05f0cf))
### Features
* add basic field type runtime validators ([#113](https://github.com/expo/entity/issues/113)) ([6c4d4b0](https://github.com/expo/entity/commit/6c4d4b03b5404dd776a12c5eb6f0af77a8c964f8))
- add basic field type runtime validators ([#113](https://github.com/expo/entity/issues/113)) ([6c4d4b0](https://github.com/expo/entity/commit/6c4d4b03b5404dd776a12c5eb6f0af77a8c964f8))
# [0.12.0](https://github.com/expo/entity/compare/v0.11.0...v0.12.0) (2021-01-22)
### Bug Fixes
* move runInTransaction to ViewerContext ([#108](https://github.com/expo/entity/issues/108)) ([b7309e1](https://github.com/expo/entity/commit/b7309e18a2dd780b64cd306d566de1f12bf8df3a))
* remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
- move runInTransaction to ViewerContext ([#108](https://github.com/expo/entity/issues/108)) ([b7309e1](https://github.com/expo/entity/commit/b7309e18a2dd780b64cd306d566de1f12bf8df3a))
- remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
### Features
* better typing on filterMap for use with predicates ([#106](https://github.com/expo/entity/issues/106)) ([01b3bfe](https://github.com/expo/entity/commit/01b3bfef7c6da55e7d89d8cccb52a0050187aee8))
- better typing on filterMap for use with predicates ([#106](https://github.com/expo/entity/issues/106)) ([01b3bfe](https://github.com/expo/entity/commit/01b3bfef7c6da55e7d89d8cccb52a0050187aee8))
# [0.11.0](https://github.com/expo/entity/compare/v0.10.0...v0.11.0) (2020-12-24)
### Bug Fixes
* add type generic default in test utils ([#104](https://github.com/expo/entity/issues/104)) ([fe2917e](https://github.com/expo/entity/commit/fe2917e6b5af808f166aa354d476e168c41e6d21))
* maps utils invariants for collections containing undefined values ([#103](https://github.com/expo/entity/issues/103)) ([29c1a43](https://github.com/expo/entity/commit/29c1a434ab6cd8d30e9f0e7c742065c8d46d8d8a))
* remove any cast from EntityLoader ([#100](https://github.com/expo/entity/issues/100)) ([b8e07f9](https://github.com/expo/entity/commit/b8e07f9ddc4077768980c000d61f5ddcc824e2e3))
- add type generic default in test utils ([#104](https://github.com/expo/entity/issues/104)) ([fe2917e](https://github.com/expo/entity/commit/fe2917e6b5af808f166aa354d476e168c41e6d21))
- maps utils invariants for collections containing undefined values ([#103](https://github.com/expo/entity/issues/103)) ([29c1a43](https://github.com/expo/entity/commit/29c1a434ab6cd8d30e9f0e7c742065c8d46d8d8a))
- remove any cast from EntityLoader ([#100](https://github.com/expo/entity/issues/100)) ([b8e07f9](https://github.com/expo/entity/commit/b8e07f9ddc4077768980c000d61f5ddcc824e2e3))
### Features
* better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
* better database adapter error handling ([#101](https://github.com/expo/entity/issues/101)) ([5208aee](https://github.com/expo/entity/commit/5208aeedb0192f0460ba3d575770a04175f5c3aa))
- better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
- better database adapter error handling ([#101](https://github.com/expo/entity/issues/101)) ([5208aee](https://github.com/expo/entity/commit/5208aeedb0192f0460ba3d575770a04175f5c3aa))
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
### Bug Fixes
* fix null handling in load paths (try 2) ([#96](https://github.com/expo/entity/issues/96)) ([d960329](https://github.com/expo/entity/commit/d9603298e0cc5c3c2b0cca959876e9195555fb57))
- fix null handling in load paths (try 2) ([#96](https://github.com/expo/entity/issues/96)) ([d960329](https://github.com/expo/entity/commit/d9603298e0cc5c3c2b0cca959876e9195555fb57))
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
### Bug Fixes
* edge deletion behavior cyclic import structure again ([#93](https://github.com/expo/entity/issues/93)) ([012c05d](https://github.com/expo/entity/commit/012c05d62d2ecd559eb331c6ca358052bfe66dc8))
- edge deletion behavior cyclic import structure again ([#93](https://github.com/expo/entity/issues/93)) ([012c05d](https://github.com/expo/entity/commit/012c05d62d2ecd559eb331c6ca358052bfe66dc8))
# [0.9.0](https://github.com/expo/entity/compare/v0.8.1...v0.9.0) (2020-11-05)
### Bug Fixes
* edge deletion behavior cyclic import structure ([#89](https://github.com/expo/entity/issues/89)) ([cca8fc1](https://github.com/expo/entity/commit/cca8fc19870057ebc7033081b59fab4a142fbf1a))
- edge deletion behavior cyclic import structure ([#89](https://github.com/expo/entity/issues/89)) ([cca8fc1](https://github.com/expo/entity/commit/cca8fc19870057ebc7033081b59fab4a142fbf1a))
### Features
* add ability to invalidate an entity manually ([#91](https://github.com/expo/entity/issues/91)) ([d1a6f13](https://github.com/expo/entity/commit/d1a6f1339648942f12b3fb605eb329aefe5cdd63))
- add ability to invalidate an entity manually ([#91](https://github.com/expo/entity/issues/91)) ([d1a6f13](https://github.com/expo/entity/commit/d1a6f1339648942f12b3fb605eb329aefe5cdd63))
## [0.8.1](https://github.com/expo/entity/compare/v0.8.0...v0.8.1) (2020-10-09)
**Note:** Version bump only for package root
# [0.8.0](https://github.com/expo/entity/compare/v0.7.1...v0.8.0) (2020-10-09)
### Features
* add entityClassName to metrics adapter events ([#83](https://github.com/expo/entity/issues/83)) ([f4cdc01](https://github.com/expo/entity/commit/f4cdc01c6cb8868c24ce3da2274f0901687e05bd))
- add entityClassName to metrics adapter events ([#83](https://github.com/expo/entity/issues/83)) ([f4cdc01](https://github.com/expo/entity/commit/f4cdc01c6cb8868c24ce3da2274f0901687e05bd))
## [0.7.1](https://github.com/expo/entity/compare/v0.7.0...v0.7.1) (2020-10-01)
### Bug Fixes
* correct type of runInTransactionAsync ([#81](https://github.com/expo/entity/issues/81)) ([f98f7d1](https://github.com/expo/entity/commit/f98f7d14b6149a496a7a3e97dd63d89b2842cc26))
- correct type of runInTransactionAsync ([#81](https://github.com/expo/entity/issues/81)) ([f98f7d1](https://github.com/expo/entity/commit/f98f7d14b6149a496a7a3e97dd63d89b2842cc26))
# [0.7.0](https://github.com/expo/entity/compare/v0.6.0...v0.7.0) (2020-10-01)
### Bug Fixes
* fix incorrect loader types for field selecitons ([#72](https://github.com/expo/entity/issues/72)) ([cd2df8d](https://github.com/expo/entity/commit/cd2df8da4b81bcd4769e950d3124c18886df2816))
* fix index export for EntityMutationTrigger and EntityMutationValidator ([#73](https://github.com/expo/entity/issues/73)) ([7e829f3](https://github.com/expo/entity/commit/7e829f30fde0bd343cb8a584ccda5530cf61ec33))
* move cache invalidation to after transaction commit ([#77](https://github.com/expo/entity/issues/77)) ([dbc3c81](https://github.com/expo/entity/commit/dbc3c81b359a668b5134189ce6de49b5893bb5e7))
* upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
- fix incorrect loader types for field selecitons ([#72](https://github.com/expo/entity/issues/72)) ([cd2df8d](https://github.com/expo/entity/commit/cd2df8da4b81bcd4769e950d3124c18886df2816))
- fix index export for EntityMutationTrigger and EntityMutationValidator ([#73](https://github.com/expo/entity/issues/73)) ([7e829f3](https://github.com/expo/entity/commit/7e829f30fde0bd343cb8a584ccda5530cf61ec33))
- move cache invalidation to after transaction commit ([#77](https://github.com/expo/entity/issues/77)) ([dbc3c81](https://github.com/expo/entity/commit/dbc3c81b359a668b5134189ce6de49b5893bb5e7))
- upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
# [0.6.0](https://github.com/expo/entity/compare/v0.5.2...v0.6.0) (2020-07-23)
### Bug Fixes
* ensure transitive deletion triggers are run for CASCADE_DELETE_INVALIDATE_CACHE ([#66](https://github.com/expo/entity/issues/66)) ([5744c51](https://github.com/expo/entity/commit/5744c51e6b3178bd9f229f3f465b683bb4474c8a))
* separate out EntityMutationValidator ([#68](https://github.com/expo/entity/issues/68)) ([547a1ef](https://github.com/expo/entity/commit/547a1efcecd17cc085702d1a3e9888ce5b644b13))
* update setField to correct typing ([#62](https://github.com/expo/entity/issues/62)) ([7a77afc](https://github.com/expo/entity/commit/7a77afc83ea732c9b062a5d6865eff9d3131d015))
- ensure transitive deletion triggers are run for CASCADE_DELETE_INVALIDATE_CACHE ([#66](https://github.com/expo/entity/issues/66)) ([5744c51](https://github.com/expo/entity/commit/5744c51e6b3178bd9f229f3f465b683bb4474c8a))
- separate out EntityMutationValidator ([#68](https://github.com/expo/entity/issues/68)) ([547a1ef](https://github.com/expo/entity/commit/547a1efcecd17cc085702d1a3e9888ce5b644b13))
- update setField to correct typing ([#62](https://github.com/expo/entity/issues/62)) ([7a77afc](https://github.com/expo/entity/commit/7a77afc83ea732c9b062a5d6865eff9d3131d015))
### Features
* entity mutation validators ([#67](https://github.com/expo/entity/issues/67)) ([fc4377d](https://github.com/expo/entity/commit/fc4377d8839da07417b88afc138f73556383d896))
* mutation triggers ([#65](https://github.com/expo/entity/issues/65)) ([fd6060c](https://github.com/expo/entity/commit/fd6060cc844f60635b3ce4c400c4877f2df8fa44))
- entity mutation validators ([#67](https://github.com/expo/entity/issues/67)) ([fc4377d](https://github.com/expo/entity/commit/fc4377d8839da07417b88afc138f73556383d896))
- mutation triggers ([#65](https://github.com/expo/entity/issues/65)) ([fd6060c](https://github.com/expo/entity/commit/fd6060cc844f60635b3ce4c400c4877f2df8fa44))
## [0.5.2](https://github.com/expo/entity/compare/v0.5.1...v0.5.2) (2020-07-02)
### Bug Fixes
* fix stub cache key generation for global cache ([#59](https://github.com/expo/entity/issues/59)) ([7c180e8](https://github.com/expo/entity/commit/7c180e84d9a8cdfd9380fac0eab2d02a96ec18f3))
- fix stub cache key generation for global cache ([#59](https://github.com/expo/entity/issues/59)) ([7c180e8](https://github.com/expo/entity/commit/7c180e84d9a8cdfd9380fac0eab2d02a96ec18f3))
## [0.5.1](https://github.com/expo/entity/compare/v0.5.0...v0.5.1) (2020-07-01)
### Bug Fixes
* correct some typing issues and exports ([#57](https://github.com/expo/entity/issues/57)) ([e91cfba](https://github.com/expo/entity/commit/e91cfba99321c2c4da2078aafd8627cbf0ceca50))
- correct some typing issues and exports ([#57](https://github.com/expo/entity/issues/57)) ([e91cfba](https://github.com/expo/entity/commit/e91cfba99321c2c4da2078aafd8627cbf0ceca50))
# 0.5.0 (2020-07-01)
### Bug Fixes
* support number ID field generation in StubDatabaseAdapter ([#43](https://github.com/expo/entity/issues/43)) ([60cc0fb](https://github.com/expo/entity/commit/60cc0fbda460c3ada1f5244a2780f90382fe3ebd))
* upgrade Apollo server for security advisory ([#38](https://github.com/expo/entity/issues/38)) ([d25c1e1](https://github.com/expo/entity/commit/d25c1e142d52385725593f4f155d1795697ab490))
- support number ID field generation in StubDatabaseAdapter ([#43](https://github.com/expo/entity/issues/43)) ([60cc0fb](https://github.com/expo/entity/commit/60cc0fbda460c3ada1f5244a2780f90382fe3ebd))
- upgrade Apollo server for security advisory ([#38](https://github.com/expo/entity/issues/38)) ([d25c1e1](https://github.com/expo/entity/commit/d25c1e142d52385725593f4f155d1795697ab490))
### Features
* add two fan out methods to association loader ([#52](https://github.com/expo/entity/issues/52)) ([89cfb3d](https://github.com/expo/entity/commit/89cfb3d5a01d4da90f64acc52ae1b839ba348a35))
* support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
- add two fan out methods to association loader ([#52](https://github.com/expo/entity/issues/52)) ([89cfb3d](https://github.com/expo/entity/commit/89cfb3d5a01d4da90f64acc52ae1b839ba348a35))
- support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
# 0.4.0 (2020-06-03)
* All Packages
* Documentation Improvements
* CI & Coverage
* TypeScript 3.9 ([#18](https://github.com/expo/entity/pull/18))
* `@expo/entity`
* Enforcing mutator convenience functions ([#5](https://github.com/expo/entity/pull/5))
* `loadByIDNullable` loader method ([#28](https://github.com/expo/entity/pull/28))
* Add ability to check update and delete policies outside of mutation ([#30](https://github.com/expo/entity/pull/30))
* `@expo/entity-cache-adapter-redis`
* `@expo/entity-database-adapter-knex`
* Throw when database adapter receives unexpected mutation results ([#31](https://github.com/expo/entity/pull/31))
* `@expo/entity-example`
* Initial Version
- All Packages
- Documentation Improvements
- CI & Coverage
- TypeScript 3.9 ([#18](https://github.com/expo/entity/pull/18))
- `@expo/entity`
- Enforcing mutator convenience functions ([#5](https://github.com/expo/entity/pull/5))
- `loadByIDNullable` loader method ([#28](https://github.com/expo/entity/pull/28))
- Add ability to check update and delete policies outside of mutation ([#30](https://github.com/expo/entity/pull/30))
- `@expo/entity-cache-adapter-redis`
- `@expo/entity-database-adapter-knex`
- Throw when database adapter receives unexpected mutation results ([#31](https://github.com/expo/entity/pull/31))
- `@expo/entity-example`
- Initial Version
# 0.3.0 (2020-04-28)
* `@expo/entity`
* Initial Version
* `@expo/entity-cache-adapter-redis`
* Initial Version
* `@expo/entity-database-adapter-knex`
* Initial Version
- `@expo/entity`
- Initial Version
- `@expo/entity-cache-adapter-redis`
- Initial Version
- `@expo/entity-database-adapter-knex`
- Initial Version
+22 -13
View File
@@ -4,24 +4,23 @@ Entity is a privacy-aware data layer for defining, caching, and authorizing acce
[![tests](https://github.com/expo/entity/workflows/tests/badge.svg)](https://github.com/expo/entity/actions?query=workflow%3Atests)
[![docs](https://github.com/expo/entity/workflows/docs/badge.svg)](https://expo.github.io/entity/)
[![codecov](https://codecov.io/gh/expo/entity/branch/main/graph/badge.svg)](https://codecov.io/gh/expo/entity)
[![codecov](https://codecov.io/gh/expo/entity/graph/badge.svg?token=4cFAAdINbk)](https://codecov.io/gh/expo/entity)
[![npm](https://img.shields.io/npm/v/@expo/entity)](https://www.npmjs.com/package/@expo/entity)
[![NPM](https://img.shields.io/npm/l/@expo/entity)](https://www.npmjs.com/package/@expo/entity)
## Core Features
- Declarative actor authorization using Privacy Policies
- Configurable data storage using Database Adapters
- Configurable, optional full-object caching using Cache Adapters
- [Dataloader](https://github.com/graphql/dataloader) in-memory caching
- Well-typed model declaration
- Typescript ORM.
- Declarative actor pre-authorization of all CRUD operations using Privacy Policies.
- Configurable data storage using Database Adapters.
- Configurable, optional, full-object read-through caching using Cache Adapters.
- [Dataloader](https://github.com/graphql/dataloader) in-memory batching and read-through caching.
## Getting Started
- [Example application](packages/entity-example)
- [Example application](https://github.com/expo/entity/tree/main/packages/entity-example)
- [Documentation](https://expo.github.io/entity/)
## Background
Authorization is the process of determining whether a user has access to a piece of data or a feature.
@@ -55,10 +54,7 @@ The Entity framework solves this by adding an additional property to the system:
```typescript
class PhotoPrivacyPolicy {
const readRules = [
new AllowIfOwnerRule(),
new AllowIfOrganizationPermissionRule(),
];
readRules = [new AllowIfOwnerRule(), new AllowIfOrganizationPermissionRule()];
}
// in the view, for example
@@ -71,6 +67,7 @@ async function get_photo_page(viewer: ViewerContext): string {
## Use Case
Entity is not limited in where it can or should be used, but was designed for use in a [Koa](https://koajs.com/)-like environment with a request and response. At Expo, we use Entity in the following manner:
1. A request comes into Koa router
1. Middleware initializes the Entity framework for the request
1. A `ViewerContext` is created identifying the individual making the request.
@@ -78,11 +75,23 @@ Entity is not limited in where it can or should be used, but was designed for us
Note: The entity framework instance should not be shared across multiple requests since it contains a unique memoized [Dataloader](https://github.com/graphql/dataloader#class-dataloader). A long-lived instance is prone to data synchronization issues, especially when the application is scaled horizontally and multiple shared caches would exist for the same data.
## Contributing
We control development tooling versions using [mise-en-place](https://mise.jdx.dev). If you have it installed, then:
- Running `mise run ci` will run all tests that our CI environment will run.
- Running `mise t` will show a list of mise tasks you can run with `mise run <name>`.
- Running `mise en .` will enter a shell environment which includes node and yarn.
If you don't have `mise` installed, [you can install it](https://mise.jdx.dev/installing-mise.html#https-mise-run) as easily as running `curl https://mise.run/zsh | sh`.
## Releasing
To release a new version:
1. `git checkout main`
1. `yarn lerna publish [patch|minor|major] -- --conventional-commits`
1. `npm login` (to refresh token)
1. `yarn lerna publish [patch|minor|major] --conventional-commits --force-publish`
1. In GitHub release interface, create a new release from the tag, copy changelog changes to release description.
## License
+3
View File
@@ -0,0 +1,3 @@
export default {
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
};
+7
View File
@@ -0,0 +1,7 @@
import unitConfig from './jest.config.js';
export default {
...unitConfig,
coverageDirectory: 'coverage-integration',
testMatch: ['**/__integration-tests__/**/*-test.ts'],
};
+11
View File
@@ -0,0 +1,11 @@
import { stripTypeScriptTypes } from 'node:module';
export function process(sourceText, sourcePath) {
return {
code: stripTypeScriptTypes(sourceText, {
mode: 'transform',
sourceMap: true,
sourceUrl: sourcePath,
}),
};
}
+16
View File
@@ -0,0 +1,16 @@
export default {
// This is an absolute path determined at runtime so that running `yarn test`
// both from each package's directory and from root works correctly.
transform: { '\\.[jt]sx?$': new URL('./jest-transform.js', import.meta.url).toString() },
extensionsToTreatAsEsm: ['.ts'],
collectCoverageFrom: [
'packages/*/src/**',
'!packages/*/src/index.ts',
'!packages/entity-example/**',
'!packages/entity-codemod/**',
'!packages/entity-testing-utils/**',
'!**/__*test*__/**',
],
coverageProvider: 'v8',
testMatch: ['**/__tests__/**/*-test.ts'],
};
+3 -5
View File
@@ -1,8 +1,6 @@
{
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.28.0"
"version": "0.72.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+52
View File
@@ -0,0 +1,52 @@
[tools]
node = "24.22.1"
[tasks.yarn]
run = "yarn install --immutable"
[tasks.build]
depends = "yarn"
run = "yarn build"
[tasks.typedoc]
depends = "build"
run = "yarn typedoc"
[tasks."checks:tsc"]
depends = "yarn"
run = "yarn tsc"
[tasks."checks:lint"]
depends = "yarn"
run = "yarn lint"
[tasks."checks:format"]
depends = "yarn"
run = "yarn format-check"
[tasks."checks:barrel"]
description = "Check if barrel files are up-to-date"
depends = "yarn"
run = ["yarn ctix", '[ -z "$(git status --porcelain)" ]']
[tasks."checks:unit"]
depends = "yarn"
run = "yarn test --coverage"
[tasks."checks:integration"]
depends = "yarn"
run = "yarn integration --coverage"
[tasks.ci]
run = { task = "checks:*" }
[settings]
experimental = true # needed for postinstall hook
[hooks]
postinstall = [
# Enable yarn shim. Version controlled by package.json#packageManager
# NOTE: mise can install yarn directly, but renovate does not use mise, so we
# control yarn versions using the packageManager field.
"corepack enable yarn",
]
+16
View File
@@ -0,0 +1,16 @@
{
"targetDefaults": {
"tsc": {
"dependsOn": ["^clean", "clean", "^tsc"]
},
"build": {
"dependsOn": ["^clean", "clean", "^build"]
},
"test": {
"dependsOn": ["^tsc"]
},
"integration": {
"dependsOn": ["^tsc"]
}
}
}
+95
View File
@@ -0,0 +1,95 @@
import { defineConfig } from 'oxlint';
import node from 'oxlint-config-universe/node';
import typescriptAnalysis from 'oxlint-config-universe/typescript-analysis';
export default defineConfig({
extends: [node, typescriptAnalysis],
plugins: ['jest'],
options: {
typeAware: true,
},
rules: {
// Overrides from oxlint-config-universe defaults
eqeqeq: ['warn', 'always'],
'no-restricted-properties': [
'warn',
{
object: 'it',
property: 'only',
message: 'it.only should not be committed to main.',
},
{
object: 'test',
property: 'only',
message: 'test.only should not be committed to main.',
},
{
object: 'describe',
property: 'only',
message: 'describe.only should not be committed to main.',
},
{
object: 'it',
property: 'skip',
message: 'it.skip should not be committed to main.',
},
{
object: 'test',
property: 'skip',
message: 'test.skip should not be committed to main.',
},
{
object: 'describe',
property: 'skip',
message: 'describe.skip should not be committed to main.',
},
],
'no-console': 'warn',
'no-void': ['warn', { allowAsStatement: true }],
'no-restricted-imports': ['error', { paths: ['.', '..', '../..'] }],
// TypeScript rules
'typescript/ban-ts-comment': 'warn',
'typescript/unbound-method': 'warn',
'typescript/restrict-template-expressions': 'off',
'typescript/no-base-to-string': 'off',
'typescript/no-redundant-type-constituents': 'off',
'typescript/require-array-sort-compare': 'off',
'typescript/explicit-function-return-type': ['warn', { allowExpressions: true }],
// Jest rules
'jest/valid-title': 'off',
'jest/expect-expect': 'off',
'jest/no-conditional-expect': 'off',
'jest/no-export': 'off',
'jest/require-to-throw-message': 'off',
},
overrides: [
{
files: ['*.config.js'],
env: { node: true },
globals: { URL: 'readonly' },
},
{
files: ['**/__tests__/**/*.ts', '**/__integration-tests__/**/*.ts'],
rules: {
'typescript/no-confusing-void-expression': 'off',
'typescript/unbound-method': 'off',
},
},
{
files: ['packages/**/*.ts'],
excludeFiles: ['**/*tests__/**', 'packages/entity-codemod/src/transforms/**'],
rules: {
'no-restricted-exports': ['error', { restrictDefaultExports: { direct: true } }],
},
},
],
ignorePatterns: [
'packages/entity-codemod/**/__testfixtures__/**',
'packages/*/build',
'coverage',
'coverage-integration',
'doc',
],
});
+31 -44
View File
@@ -4,52 +4,39 @@
"workspaces": [
"packages/*"
],
"type": "module",
"scripts": {
"tsc": "lerna run tsc",
"clean": "lerna run clean",
"lint": "lerna run lint",
"lint-fix": "lerna run lint-fix",
"test": "lerna run test",
"integration": "./resources/run-with-docker lerna run integration-no-setup",
"prepublish": "lerna link && lerna run clean",
"prepare": "lerna run tsc",
"barrelsby": "lerna run barrelsby"
"tsc": "tsc --build",
"build": "tsc --build",
"clean": "yarn build --clean",
"lint": "oxlint --type-aware . --max-warnings=0 && yarn format-check",
"lint-fix": "oxlint --type-aware --fix . && yarn format",
"format": "oxfmt --write --ignore-path .fmtignore .",
"format-check": "oxfmt --check --ignore-path .fmtignore .",
"test": "yarn build --noCheck && NODE_OPTIONS='--experimental-vm-modules --disable-warning=ExperimentalWarning' jest",
"test:all": "yarn test",
"integration": "./resources/run-with-docker yarn test --config jest-integration.config.js --runInBand",
"integration:all": "yarn integration",
"ctix": "ctix build && resources/prepend-barrels.sh",
"package-files": "yarn workspaces foreach --all --no-private --parallel --topological pack --dry-run",
"typedoc": "yarn build && typedoc"
},
"devDependencies": {
"@expo/entity": "file:packages/entity",
"@expo/results": "^1.0.0",
"@types/invariant": "^2.2.33",
"@types/jest": "^26.0.10",
"@types/jsbn": "^1.2.29",
"@types/lru-cache": "^5.1.1",
"@types/node": "^14.6.0",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"barrelsby": "^2.2.0",
"eslint": "^7.7.0",
"eslint-config-universe": "^7.0.0",
"eslint-plugin-tsdoc": "^0.2.16",
"ioredis": "^5.0.4",
"jest": "^26.6.3",
"lerna": "^5.4.0",
"lru-cache": "^6.0.0",
"nullthrows": "^1.1.1",
"pg": "^8.6.0",
"prettier": "^2.3.2",
"ts-jest": "~26.4.4",
"ts-mockito": "^2.6.1",
"typedoc": "^0.23.10",
"typescript": "^4.6.3",
"uuid": "^8.3.0"
"@tsconfig/bases": "1.0.28",
"@types/node": "24.19.2",
"ctix": "2.8.2",
"jest": "30.5.2",
"lerna": "9.0.7",
"oxfmt": "0.73.0",
"oxlint": "1.88.0",
"oxlint-config-universe": "0.2.0",
"oxlint-tsgolint": "0.25.0",
"prettier": "3.9.10",
"typedoc": "0.28.20",
"typescript": "6.0.3"
},
"dependencies": {
"@expo/entity": "file:packages/entity",
"@expo/entity-cache-adapter-local-memory": "file:packages/entity-cache-adapter-local-memory",
"@expo/entity-cache-adapter-redis": "file:packages/entity-cache-adapter-redis",
"@expo/entity-database-adapter-knex": "file:packages/entity-database-adapter-knex",
"@expo/entity-ip-address-field": "file:packages/entity-ip-address-field",
"@expo/entity-secondary-cache-local-memory": "file:packages/entity-secondary-cache-local-memory",
"@expo/entity-secondary-cache-redis": "file:packages/entity-secondary-cache-redis"
}
"resolutions": {
"collect-v8-coverage": "^1.0.3"
},
"packageManager": "[email protected]+sha512.b2e1e7524f654f2749d32b4ebcb4622473cb5bcbc485df2007e12a154e50162a4d795526768bc5f5b8f81717bfd79deb2472813d86fb5ae2eb551fa9c872b08f"
}
@@ -3,82 +3,242 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
### Bug Fixes
- escape all matching portions of parts of cacher cache keys ([#486](https://github.com/expo/entity/issues/486)) ([21f2f6f](https://github.com/expo/entity/commit/21f2f6f461ee771e8b1c1b22dbaf9b275094c0db))
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.55.0](https://github.com/expo/entity/compare/v0.54.0...v0.55.0) (2026-01-27)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.54.0](https://github.com/expo/entity/compare/v0.53.0...v0.54.0) (2026-01-21)
### Bug Fixes
- **deps:** pin dependencies ([#328](https://github.com/expo/entity/issues/328)) ([5679f27](https://github.com/expo/entity/commit/5679f27209f1515ca5e626d858c4aa054b414625))
# [0.53.0](https://github.com/expo/entity/compare/v0.52.0...v0.53.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.52.0](https://github.com/expo/entity/compare/v0.51.0...v0.52.0) (2025-12-19)
### Features
- switch local cache to an interface ([#322](https://github.com/expo/entity/issues/322)) ([c453726](https://github.com/expo/entity/commit/c4537262d810ea01e519f9aec6ae5b17a54ce936))
# [0.51.0](https://github.com/expo/entity/compare/v0.50.0...v0.51.0) (2025-11-21)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.50.0](https://github.com/expo/entity/compare/v0.49.0...v0.50.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.49.0](https://github.com/expo/entity/compare/v0.48.0...v0.49.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.48.0](https://github.com/expo/entity/compare/v0.47.0...v0.48.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.47.0](https://github.com/expo/entity/compare/v0.46.0...v0.47.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.46.0](https://github.com/expo/entity/compare/v0.45.0...v0.46.0) (2025-06-26)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.45.0](https://github.com/expo/entity/compare/v0.44.0...v0.45.0) (2025-06-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.44.0](https://github.com/expo/entity/compare/v0.43.0...v0.44.0) (2025-05-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.43.0](https://github.com/expo/entity/compare/v0.42.0...v0.43.0) (2025-04-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.42.0](https://github.com/expo/entity/compare/v0.41.0...v0.42.0) (2025-04-10)
### Features
- add @expo/entity-testing-utils package ([#280](https://github.com/expo/entity/issues/280)) ([485894a](https://github.com/expo/entity/commit/485894af16e233d533a9480285bbda56812cbe0b))
- add composite field loading and caching ([#272](https://github.com/expo/entity/issues/272)) ([f0aa0da](https://github.com/expo/entity/commit/f0aa0dafebdb56418cbd22dda437233d850db3e7))
- change entity id field generic to field name and derive type where necessary ([#278](https://github.com/expo/entity/issues/278)) ([b7e524c](https://github.com/expo/entity/commit/b7e524c41892797608d0b884410b8d520a80a9ef))
- convert batched/cached loader interface to holder pattern ([#271](https://github.com/expo/entity/issues/271)) ([06b3cb7](https://github.com/expo/entity/commit/06b3cb7f8076ef9e5b83cdf154da159801bd14cc)), closes [#201](https://github.com/expo/entity/issues/201)
- invalidate n+/-1 cacheKeyVersion for entities for push safety ([#275](https://github.com/expo/entity/issues/275)) ([d9c1852](https://github.com/expo/entity/commit/d9c1852706f98f0761ff24ddd244e4ed2beca580))
# [0.41.0](https://github.com/expo/entity/compare/v0.40.0...v0.41.0) (2025-03-07)
- feat!: Default all loaders and mutators to enforcing (#256) ([a976e76](https://github.com/expo/entity/commit/a976e765098bca27049585e43a3bed5c71b9ac6b)), closes [#256](https://github.com/expo/entity/issues/256)
### BREAKING CHANGES
- Default all loaders and mutators to enforcing
# [0.40.0](https://github.com/expo/entity/compare/v0.39.0...v0.40.0) (2025-02-27)
### Reverts
- Revert "v0.40.0" ([fa78ce8](https://github.com/expo/entity/commit/fa78ce84e2f2eb8199e36fd51779fc68905001c0))
# [0.39.0](https://github.com/expo/entity/compare/v0.38.0...v0.39.0) (2024-11-21)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.38.0](https://github.com/expo/entity/compare/v0.37.0...v0.38.0) (2024-06-27)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.37.0](https://github.com/expo/entity/compare/v0.36.0...v0.37.0) (2024-06-12)
### Bug Fixes
- update barrels ([#241](https://github.com/expo/entity/issues/241)) ([8c5f81b](https://github.com/expo/entity/commit/8c5f81bcca107e9b7bbea5f0ab41b0105057806e))
# [0.36.0](https://github.com/expo/entity/compare/v0.35.0...v0.36.0) (2024-06-12)
### Bug Fixes
- constrain entity fields type to string-keyed object ([#235](https://github.com/expo/entity/issues/235)) ([7e2cea1](https://github.com/expo/entity/commit/7e2cea16973a0ae1917f867cd25d6ef7c8eaecef))
# [0.35.0](https://github.com/expo/entity/compare/v0.34.0...v0.35.0) (2024-04-11)
### Bug Fixes
- Revert: require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([1bdfbd6](https://github.com/expo/entity/commit/1bdfbd6b562d1200e4029df0533b5adb2d917831))
# [0.34.0](https://github.com/expo/entity/compare/v0.33.0...v0.34.0) (2024-04-11)
### Features
- require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([8b0b31f](https://github.com/expo/entity/commit/8b0b31fdde5bd565aa527719003ef283a45f55cc))
# [0.33.0](https://github.com/expo/entity/compare/v0.32.0...v0.33.0) (2023-10-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.32.0](https://github.com/expo/entity/compare/v0.31.1...v0.32.0) (2023-06-10)
### Features
- entity companion definition static thunk ([#210](https://github.com/expo/entity/issues/210)) ([4b18010](https://github.com/expo/entity/commit/4b18010d42be50ef329f428b08330e21bf676586))
# [0.31.0](https://github.com/expo/entity/compare/v0.30.0...v0.31.0) (2023-01-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.30.0](https://github.com/expo/entity/compare/v0.29.0...v0.30.0) (2022-12-23)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.29.0](https://github.com/expo/entity/compare/v0.28.0...v0.29.0) (2022-10-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.27.0](https://github.com/expo/entity/compare/v0.26.1...v0.27.0) (2022-08-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
## [0.26.1](https://github.com/expo/entity/compare/v0.26.0...v0.26.1) (2022-07-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.26.0](https://github.com/expo/entity/compare/v0.25.3...v0.26.0) (2022-07-14)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
## [0.25.3](https://github.com/expo/entity/compare/v0.25.2...v0.25.3) (2022-03-12)
### Bug Fixes
* lock lru-cache to v6.0.0 ([#170](https://github.com/expo/entity/issues/170)) ([293868b](https://github.com/expo/entity/commit/293868b1a73df9f749216af68b1ae94ac22ac2b9))
- lock lru-cache to v6.0.0 ([#170](https://github.com/expo/entity/issues/170)) ([293868b](https://github.com/expo/entity/commit/293868b1a73df9f749216af68b1ae94ac22ac2b9))
## [0.25.2](https://github.com/expo/entity/compare/v0.25.1...v0.25.2) (2022-03-12)
### Bug Fixes
* lru-cache yarn workspace version resolution ([#168](https://github.com/expo/entity/issues/168)) ([e76ca8c](https://github.com/expo/entity/commit/e76ca8c2f72da84aa372a64ef1898ade220ed0a8))
- lru-cache yarn workspace version resolution ([#168](https://github.com/expo/entity/issues/168)) ([e76ca8c](https://github.com/expo/entity/commit/e76ca8c2f72da84aa372a64ef1898ade220ed0a8))
## [0.25.1](https://github.com/expo/entity/compare/v0.25.0...v0.25.1) (2022-03-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-local-memory
# [0.25.0](https://github.com/expo/entity/compare/v0.24.0...v0.25.0) (2022-03-10)
### Features
* composed cache adapter, composed secondary cache, local memory secondary cache ([#164](https://github.com/expo/entity/issues/164)) ([6b6e70e](https://github.com/expo/entity/commit/6b6e70e75f7e42a4dce13edeb406a4d88d9264f0))
- composed cache adapter, composed secondary cache, local memory secondary cache ([#164](https://github.com/expo/entity/issues/164)) ([6b6e70e](https://github.com/expo/entity/commit/6b6e70e75f7e42a4dce13edeb406a4d88d9264f0))
# [0.24.0](https://github.com/expo/entity/compare/v0.23.0...v0.24.0) (2022-02-16)
### Features
* add local memory cache package ([#158](https://github.com/expo/entity/issues/158)) ([85d3575](https://github.com/expo/entity/commit/85d35752fa0919a62efb776a762a6829e83ca7af))
- add local memory cache package ([#158](https://github.com/expo/entity/issues/158)) ([85d3575](https://github.com/expo/entity/commit/85d35752fa0919a62efb776a762a6829e83ca7af))
@@ -1,38 +1,40 @@
{
"name": "@expo/entity-cache-adapter-local-memory",
"version": "0.28.0",
"version": "0.72.0",
"description": "Cross-request local memory cache adapter for @expo/entity",
"files": [
"build",
"src"
],
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"tsc": "tsc",
"clean": "rm -rf build coverage coverage-integration",
"lint": "eslint src",
"lint-fix": "eslint src --fix",
"test": "jest --rootDir . --config ../../resources/jest.config.js",
"integration": "../../resources/run-with-docker yarn integration-no-setup",
"integration-no-setup": "jest --config ../../resources/jest-integration.config.js --rootDir . --runInBand --passWithNoTests",
"barrelsby": "barrelsby --directory src --location top --exclude tests__ --singleQuotes --exportDefault --delete"
},
"engines": {
"node": ">=12"
},
"keywords": [
"entity"
],
"author": "Expo",
"license": "MIT",
"peerDependencies": {
"@expo/entity": "*"
"author": "Expo",
"files": [
"build",
"!*.tsbuildinfo",
"!__*",
"src"
],
"type": "module",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"scripts": {
"build": "tsc --build",
"prepack": "rm -rf build && yarn build",
"clean": "yarn build --clean",
"lint": "yarn run --top-level oxlint --type-aware src",
"lint-fix": "yarn lint --fix",
"test": "yarn test:all --rootDir $(pwd)",
"integration": "yarn integration:all --rootDir $(pwd)"
},
"dependencies": {
"lru-cache": "^6.0.0"
"@expo/entity": "workspace:^"
},
"devDependencies": {
"@expo/entity": "^0.28.0"
"@expo/entity-testing-utils": "workspace:^",
"@isaacs/ttlcache": "2.1.5",
"@jest/globals": "30.5.2",
"typescript": "6.0.3"
},
"engines": {
"node": ">=18"
}
}
@@ -1,37 +1,71 @@
import { CacheLoadResult, CacheStatus, IEntityGenericCacher } from '@expo/entity';
import LRUCache from 'lru-cache';
import type {
CacheLoadResult,
EntityConfiguration,
IEntityGenericCacher,
IEntityLoadKey,
IEntityLoadValue,
} from '@expo/entity';
import { CacheStatus } from '@expo/entity';
// Sentinel value we store in local memory to negatively cache a database miss.
// The sentinel value is distinct from any (positively) cached value.
export const DOES_NOT_EXIST_LOCAL_MEMORY_CACHE = Symbol('doesNotExist');
type LocalMemoryCacheValue<TFields> = Readonly<TFields> | typeof DOES_NOT_EXIST_LOCAL_MEMORY_CACHE;
export type LocalMemoryCache<TFields> = LRUCache<string, LocalMemoryCacheValue<TFields>>;
export default class GenericLocalMemoryCacher<TFields> implements IEntityGenericCacher<TFields> {
constructor(private readonly localMemoryCache: LocalMemoryCache<TFields>) {}
/**
* Type of value stored in the local memory cache. This is either the cached fields, or the
* DOES_NOT_EXIST_LOCAL_MEMORY_CACHE sentinel value.
*/
export type LocalMemoryCacheValue<TFields extends Record<string, any>> =
| Readonly<TFields>
| typeof DOES_NOT_EXIST_LOCAL_MEMORY_CACHE;
static createLRUCache<TFields>(
options: { maxSize?: number; ttlSeconds?: number } = {}
): LocalMemoryCache<TFields> {
const DEFAULT_LRU_CACHE_MAX_AGE_SECONDS = 10;
const DEFAULT_LRU_CACHE_SIZE = 10000;
const maxAgeSeconds = options.ttlSeconds ?? DEFAULT_LRU_CACHE_MAX_AGE_SECONDS;
return new LRUCache<string, LocalMemoryCacheValue<TFields>>({
max: options.maxSize ?? DEFAULT_LRU_CACHE_SIZE,
length: (value) => (value === DOES_NOT_EXIST_LOCAL_MEMORY_CACHE ? 0 : 1),
maxAge: maxAgeSeconds * 1000, // convert to ms
});
}
/**
* Interface for a local memory cache used by GenericLocalMemoryCacher. Most often, this is something like
* a TTLCache from the `@isaacs/ttlcache` package or an lru-cache.
*/
export interface ILocalMemoryCache<TFields extends Record<string, any>> {
/**
* Gets a value from the cache for specified key.
* @param key - key to get
* @returns the cached value, or undefined if not present
*/
get(key: string): LocalMemoryCacheValue<TFields> | undefined;
static createNoOpCache<TFields>(): LocalMemoryCache<TFields> {
return new LRUCache<string, LocalMemoryCacheValue<TFields>>({
max: 0,
maxAge: -1,
});
/**
* Sets a value in the cache for specified key.
* @param key - key to set
* @param value - value to set
*/
set(key: string, value: LocalMemoryCacheValue<TFields>): void;
/**
* Deletes a value from the cache for specified key.
* @param key - key to delete
*/
delete(key: string): void;
}
export class GenericLocalMemoryCacher<
TFields extends Record<string, any>,
TIDField extends keyof TFields,
> implements IEntityGenericCacher<TFields, TIDField> {
constructor(
private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
private readonly localMemoryCache: ILocalMemoryCache<TFields>,
) {}
static createNoOpCache<TFields extends Record<string, any>>(): ILocalMemoryCache<TFields> {
return {
get(_key: string): LocalMemoryCacheValue<TFields> | undefined {
return undefined;
},
set(_key: string, _value: LocalMemoryCacheValue<TFields>): void {},
delete(_key: string): void {},
};
}
public async loadManyAsync(
keys: readonly string[]
keys: readonly string[],
): Promise<ReadonlyMap<string, CacheLoadResult<TFields>>> {
const cacheResults = new Map<string, CacheLoadResult<TFields>>();
for (const key of keys) {
@@ -68,15 +102,38 @@ export default class GenericLocalMemoryCacher<TFields> implements IEntityGeneric
public async invalidateManyAsync(keys: readonly string[]): Promise<void> {
for (const key of keys) {
this.localMemoryCache.del(key);
this.localMemoryCache.delete(key);
}
}
public makeCacheKey(parts: string[]): string {
public makeCacheKeyForStorage<
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
TSerializedLoadValue,
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
>(key: TLoadKey, value: TLoadValue): string {
const cacheKeyType = key.getLoadMethodType();
const keyAndValueParts = key.createCacheKeyPartsForLoadValue(this.entityConfiguration, value);
const parts = [
this.entityConfiguration.tableName,
cacheKeyType,
`${this.entityConfiguration.cacheKeyVersion}`,
...keyAndValueParts,
];
const delimiter = ':';
const escapedParts = parts.map((part) =>
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
part.replaceAll('\\', '\\\\').replaceAll(delimiter, `\\${delimiter}`),
);
return escapedParts.join(delimiter);
}
public makeCacheKeysForInvalidation<
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
TSerializedLoadValue,
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
>(key: TLoadKey, value: TLoadValue): readonly string[] {
// for local memory caches, we don't need to invalidate old versions of the cache keys
// since they are not persisted across deploys
return [this.makeCacheKeyForStorage(key, value)];
}
}
@@ -1,80 +0,0 @@
import { EntityCacheAdapter, CacheLoadResult, EntityConfiguration, mapKeys } from '@expo/entity';
import invariant from 'invariant';
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
export default class LocalMemoryCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
private readonly genericLocalMemoryCacher: GenericLocalMemoryCacher<TFields>;
constructor(
entityConfiguration: EntityConfiguration<TFields>,
localMemoryCache: LocalMemoryCache<TFields>
) {
super(entityConfiguration);
this.genericLocalMemoryCacher = new GenericLocalMemoryCacher(localMemoryCache);
}
public async loadManyAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
const localMemoryCacheKeyToFieldValueMapping = new Map(
fieldValues.map((fieldValue) => [this.makeCacheKey(fieldName, fieldValue), fieldValue])
);
const cacheResults = await this.genericLocalMemoryCacher.loadManyAsync(
Array.from(localMemoryCacheKeyToFieldValueMapping.keys())
);
return mapKeys(cacheResults, (cacheKey) => {
const fieldValue = localMemoryCacheKeyToFieldValueMapping.get(cacheKey);
invariant(
fieldValue !== undefined,
'Unspecified cache key %s returned from generic local memory cacher',
cacheKey
);
return fieldValue;
});
}
public async cacheManyAsync<N extends keyof TFields>(
fieldName: N,
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
): Promise<void> {
await this.genericLocalMemoryCacher.cacheManyAsync(
mapKeys(objectMap, (fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
public async cacheDBMissesAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<void> {
await this.genericLocalMemoryCacher.cacheDBMissesAsync(
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
public async invalidateManyAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<void> {
await this.genericLocalMemoryCacher.invalidateManyAsync(
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
private makeCacheKey<N extends keyof TFields>(
fieldName: N,
fieldValue: NonNullable<TFields[N]>
): string {
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
invariant(columnName, `database field mapping missing for ${String(fieldName)}`);
const parts = [
this.entityConfiguration.tableName,
`${this.entityConfiguration.cacheKeyVersion}`,
columnName,
String(fieldValue),
];
return this.genericLocalMemoryCacher.makeCacheKey(parts);
}
}
@@ -1,45 +1,54 @@
import {
computeIfAbsent,
EntityCacheAdapter,
import type {
EntityConfiguration,
IEntityCacheAdapter,
IEntityCacheAdapterProvider,
} from '@expo/entity';
import { computeIfAbsent, GenericEntityCacheAdapter } from '@expo/entity';
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
import LocalMemoryCacheAdapter from './LocalMemoryCacheAdapter';
import type { ILocalMemoryCache } from './GenericLocalMemoryCacher.ts';
import { GenericLocalMemoryCacher } from './GenericLocalMemoryCacher.ts';
export default class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvider {
// local memory cache adapters should be shared/reused across requests
static localMemoryCacheAdapterMap = new Map<string, LocalMemoryCacheAdapter<any>>();
static getNoOpProvider(): IEntityCacheAdapterProvider {
return new LocalMemoryCacheAdapterProvider(<TFields>() =>
GenericLocalMemoryCacher.createNoOpCache<TFields>()
export type LocalMemoryCacheCreator = <
TFields extends Record<string, any>,
>() => ILocalMemoryCache<TFields>;
/**
* Vends local memory cache adapters. An instance of this class may be shared across requests to
* share the local memory cache.
*/
export class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvider {
/**
* @returns a no-op local memory cache adapter provider, or one that doesn't cache at all.
*/
static createNoOpProvider(): IEntityCacheAdapterProvider {
return new LocalMemoryCacheAdapterProvider(<TFields extends Record<string, any>>() =>
GenericLocalMemoryCacher.createNoOpCache<TFields>(),
);
}
static getProvider(
options: { maxSize?: number; ttlSeconds?: number } = {}
/**
* @returns a local memory cache adapter provider configured with the supplied local memory cache creator function.
*/
static createProviderWithCacheCreator(
cacheCreator: LocalMemoryCacheCreator,
): IEntityCacheAdapterProvider {
return new LocalMemoryCacheAdapterProvider(<TFields>() =>
GenericLocalMemoryCacher.createLRUCache<TFields>(options)
);
return new LocalMemoryCacheAdapterProvider(cacheCreator);
}
private constructor(
private readonly localMemoryCacheCreator: <TFields>() => LocalMemoryCache<TFields>
) {}
private readonly localMemoryCacheAdapterMap = new Map<
string,
GenericEntityCacheAdapter<any, any>
>();
public getCacheAdapter<TFields>(
entityConfiguration: EntityConfiguration<TFields>
): EntityCacheAdapter<TFields> {
return computeIfAbsent(
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap,
entityConfiguration.tableName,
() => {
const localMemoryCache = this.localMemoryCacheCreator<TFields>();
return new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
}
);
private constructor(private readonly localMemoryCacheCreator: LocalMemoryCacheCreator) {}
public getCacheAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
entityConfiguration: EntityConfiguration<TFields, TIDField>,
): IEntityCacheAdapter<TFields, TIDField> {
return computeIfAbsent(this.localMemoryCacheAdapterMap, entityConfiguration.tableName, () => {
const localMemoryCache = this.localMemoryCacheCreator<TFields>();
return new GenericEntityCacheAdapter(
new GenericLocalMemoryCacher(entityConfiguration, localMemoryCache),
);
});
}
}
@@ -1,13 +1,12 @@
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
EntityPrivacyPolicy,
ViewerContext,
UUIDField,
DateField,
StringField,
EntityConfiguration,
EntityCompanionDefinition,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
StringField,
UUIDField,
} from '@expo/entity';
export type LocalMemoryTestEntityFields = {
@@ -16,32 +15,36 @@ export type LocalMemoryTestEntityFields = {
dateField: Date | null;
};
export default class LocalMemoryTestEntity extends Entity<
export class LocalMemoryTestEntity extends Entity<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext
> {
static getCompanionDefinition(): EntityCompanionDefinition<
static defineCompanionDefinition(): EntityCompanionDefinition<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity,
LocalMemoryTestEntityPrivacyPolicy
> {
return localMemoryTestEntityCompanionDefinition;
return {
entityClass: LocalMemoryTestEntity,
entityConfiguration: localMemoryTestEntityConfiguration,
privacyPolicyClass: LocalMemoryTestEntityPrivacyPolicy,
};
}
}
export class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity
> {
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity
>(),
@@ -49,7 +52,7 @@ export class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity
>(),
@@ -57,7 +60,7 @@ export class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity
>(),
@@ -65,36 +68,32 @@ export class LocalMemoryTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<
LocalMemoryTestEntityFields,
string,
'id',
ViewerContext,
LocalMemoryTestEntity
>(),
];
}
export const localMemoryTestEntityConfiguration =
new EntityConfiguration<LocalMemoryTestEntityFields>({
idField: 'id',
tableName: 'local_memory_test_entities',
schema: {
id: new UUIDField({
columnName: 'id',
cache: true,
}),
name: new StringField({
columnName: 'name',
cache: true,
}),
dateField: new DateField({
columnName: 'date_field',
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'local-memory',
});
const localMemoryTestEntityCompanionDefinition = new EntityCompanionDefinition({
entityClass: LocalMemoryTestEntity,
entityConfiguration: localMemoryTestEntityConfiguration,
privacyPolicyClass: LocalMemoryTestEntityPrivacyPolicy,
export const localMemoryTestEntityConfiguration = new EntityConfiguration<
LocalMemoryTestEntityFields,
'id'
>({
idField: 'id',
tableName: 'local_memory_test_entities',
schema: {
id: new UUIDField({
columnName: 'id',
cache: true,
}),
name: new StringField({
columnName: 'name',
cache: true,
}),
dateField: new DateField({
columnName: 'date_field',
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'local-memory',
});
@@ -0,0 +1,63 @@
import type { IEntityMetricsAdapter } from '@expo/entity';
import { EntityCompanionProvider, NoOpEntityMetricsAdapter } from '@expo/entity';
import { StubDatabaseAdapterProvider, StubQueryContextProvider } from '@expo/entity-testing-utils';
import { TTLCache } from '@isaacs/ttlcache';
import type { ILocalMemoryCache, LocalMemoryCacheValue } from '../GenericLocalMemoryCacher.ts';
import { LocalMemoryCacheAdapterProvider } from '../LocalMemoryCacheAdapterProvider.ts';
const queryContextProvider = new StubQueryContextProvider();
function createTTLCache<TFields extends Record<string, any>>(
options: { maxSize?: number; ttlSeconds?: number } = {},
): ILocalMemoryCache<TFields> {
const DEFAULT_LRU_CACHE_MAX_AGE_SECONDS = 10;
const DEFAULT_LRU_CACHE_SIZE = 10000;
const maxAgeSeconds = options.ttlSeconds ?? DEFAULT_LRU_CACHE_MAX_AGE_SECONDS;
return new TTLCache<string, LocalMemoryCacheValue<TFields>>({
max: options.maxSize ?? DEFAULT_LRU_CACHE_SIZE,
ttl: maxAgeSeconds * 1000, // convert to ms
updateAgeOnGet: true,
});
}
export const createLocalMemoryTestEntityCompanionProvider = (
localMemoryOptions: { maxSize?: number; ttlSeconds?: number } = {},
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter(),
): EntityCompanionProvider => {
const localMemoryCacheAdapterProvider =
localMemoryOptions.maxSize === 0 && localMemoryOptions.ttlSeconds === 0
? LocalMemoryCacheAdapterProvider.createNoOpProvider()
: LocalMemoryCacheAdapterProvider.createProviderWithCacheCreator(() =>
createTTLCache(localMemoryOptions),
);
return new EntityCompanionProvider(
metricsAdapter,
new Map([
[
'postgres',
{
adapterProvider: new StubDatabaseAdapterProvider(),
queryContextProvider,
},
],
]),
new Map([
[
'local-memory',
{
cacheAdapterProvider: localMemoryCacheAdapterProvider,
},
],
]),
);
};
export const createNoOpLocalMemoryIntegrationTestEntityCompanionProvider = (
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter(),
): EntityCompanionProvider => {
return createLocalMemoryTestEntityCompanionProvider(
{ maxSize: 0, ttlSeconds: 0 },
metricsAdapter,
);
};
@@ -0,0 +1,178 @@
import type { GenericEntityCacheAdapter, IEntityGenericCacher } from '@expo/entity';
import {
CacheStatus,
SingleFieldHolder,
SingleFieldValueHolder,
ViewerContext,
} from '@expo/entity';
import { describe, expect, it } from '@jest/globals';
import { v4 as uuidv4 } from 'uuid';
import { GenericLocalMemoryCacher } from '../GenericLocalMemoryCacher.ts';
import type { LocalMemoryCacheAdapterProvider } from '../LocalMemoryCacheAdapterProvider.ts';
import type { LocalMemoryTestEntityFields } from '../__testfixtures__/LocalMemoryTestEntity.ts';
import { LocalMemoryTestEntity } from '../__testfixtures__/LocalMemoryTestEntity.ts';
import {
createLocalMemoryTestEntityCompanionProvider,
createNoOpLocalMemoryIntegrationTestEntityCompanionProvider,
} from '../__testfixtures__/createLocalMemoryTestEntityCompanionProvider.ts';
describe(GenericLocalMemoryCacher, () => {
it('has correct caching behavior', async () => {
const entityCompanionProvider = createLocalMemoryTestEntityCompanionProvider();
const viewerContext = new ViewerContext(entityCompanionProvider);
const genericCacher = (
viewerContext.entityCompanionProvider.getCompanionForEntity(LocalMemoryTestEntity)[
'tableDataCoordinator'
]['cacheAdapter'] as GenericEntityCacheAdapter<LocalMemoryTestEntityFields, 'id'>
)['genericCacher'];
const date = new Date();
const entity1Created = await LocalMemoryTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.createAsync();
// loading an entity should put it in cache
const entity1 = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
entity1Created.getID(),
);
const localMemoryCacheAdapterProvider = entityCompanionProvider['cacheAdapterFlavors'].get(
'local-memory',
)!.cacheAdapterProvider as LocalMemoryCacheAdapterProvider;
const entitySpecificGenericCacher = localMemoryCacheAdapterProvider[
'localMemoryCacheAdapterMap'
].get(
viewerContext.entityCompanionProvider.getCompanionForEntity(LocalMemoryTestEntity)
.entityCompanionDefinition.entityConfiguration.tableName,
)!['genericCacher'] as IEntityGenericCacher<LocalMemoryTestEntityFields, 'id'>;
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1.getID()),
),
]);
const cachedValue = cachedResult.get(
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1.getID()),
),
)!;
expect(cachedValue).toMatchObject({
status: CacheStatus.HIT,
item: {
id: entity1.getID(),
name: 'blah',
dateField: date,
},
});
// simulate non existent db fetch, should write negative result ('') to cache
const nonExistentId = uuidv4();
const entityNonExistentResult =
await LocalMemoryTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(nonExistentId),
),
]);
expect(
nonExistentCachedResult.get(
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(nonExistentId),
),
),
).toMatchObject({
status: CacheStatus.NEGATIVE,
});
// load again through entities framework to ensure it reads negative result
const entityNonExistentResult2 =
await LocalMemoryTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult2.ok).toBe(false);
// invalidate from cache to ensure it invalidates correctly
await LocalMemoryTestEntity.invalidationUtils(viewerContext).invalidateFieldsAsync(
entity1.getAllFields(),
);
const keys = genericCacher.makeCacheKeysForInvalidation(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1.getID()),
);
const cachedResultMiss = await entitySpecificGenericCacher.loadManyAsync(keys);
expect(Array.from(cachedResultMiss.values()).every((v) => v.status === CacheStatus.MISS)).toBe(
true,
);
});
it('respects the parameters of a noop cache', async () => {
const entityCompanionProvider = createNoOpLocalMemoryIntegrationTestEntityCompanionProvider();
const viewerContext = new ViewerContext(entityCompanionProvider);
const genericCacher = (
viewerContext.entityCompanionProvider.getCompanionForEntity(LocalMemoryTestEntity)[
'tableDataCoordinator'
]['cacheAdapter'] as GenericEntityCacheAdapter<LocalMemoryTestEntityFields, 'id'>
)['genericCacher'];
const cacheKeyMaker = genericCacher['makeCacheKeyForStorage'].bind(genericCacher);
const date = new Date();
const entity1Created = await LocalMemoryTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.createAsync();
// loading an entity will try to put it in cache but it's a noop cache, so it should be a miss
const entity1 = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
entity1Created.getID(),
);
const localMemoryCacheAdapterProvider = entityCompanionProvider['cacheAdapterFlavors'].get(
'local-memory',
)!.cacheAdapterProvider as LocalMemoryCacheAdapterProvider;
const entitySpecificGenericCacher = localMemoryCacheAdapterProvider[
'localMemoryCacheAdapterMap'
].get(
viewerContext.entityCompanionProvider.getCompanionForEntity(LocalMemoryTestEntity)
.entityCompanionDefinition.entityConfiguration.tableName,
)!['genericCacher'] as IEntityGenericCacher<LocalMemoryTestEntityFields, 'id'>;
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker(new SingleFieldHolder('id'), new SingleFieldValueHolder(entity1.getID())),
]);
const cachedValue = cachedResult.get(
cacheKeyMaker(new SingleFieldHolder('id'), new SingleFieldValueHolder(entity1.getID())),
)!;
expect(cachedValue).toMatchObject({
status: CacheStatus.MISS,
});
// a non existent db fetch should try to write negative result ('') but it's a noop cache, so it should be a miss
const nonExistentId = uuidv4();
const entityNonExistentResult =
await LocalMemoryTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker(new SingleFieldHolder('id'), new SingleFieldValueHolder(nonExistentId)),
]);
expect(
nonExistentCachedResult.get(
cacheKeyMaker(new SingleFieldHolder('id'), new SingleFieldValueHolder(nonExistentId)),
),
).toMatchObject({
status: CacheStatus.MISS,
});
});
});
@@ -1,28 +1,170 @@
import GenericLocalMemoryCacher from '../GenericLocalMemoryCacher';
import {
CacheStatus,
EntityConfiguration,
GenericEntityCacheAdapter,
SingleFieldHolder,
SingleFieldValueHolder,
SingleFieldValueHolderMap,
UUIDField,
} from '@expo/entity';
import { TTLCache } from '@isaacs/ttlcache';
import { describe, expect, it } from '@jest/globals';
describe(GenericLocalMemoryCacher, () => {
describe(GenericLocalMemoryCacher.createLRUCache, () => {
it('creates a cache with default options', () => {
const cache = GenericLocalMemoryCacher.createLRUCache();
expect(cache.max).toBe(10000);
expect(cache.maxAge).toBe(10000);
import type { ILocalMemoryCache, LocalMemoryCacheValue } from '../GenericLocalMemoryCacher.ts';
import {
DOES_NOT_EXIST_LOCAL_MEMORY_CACHE,
GenericLocalMemoryCacher,
} from '../GenericLocalMemoryCacher.ts';
type BlahFields = {
id: string;
};
const entityConfiguration = new EntityConfiguration<BlahFields, 'id'>({
idField: 'id',
tableName: 'blah',
schema: {
id: new UUIDField({ columnName: 'id', cache: true }),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'local-memory',
});
function createTTLCache<TFields extends Record<string, any>>(): ILocalMemoryCache<TFields> {
return new TTLCache<string, LocalMemoryCacheValue<TFields>>({
max: 10000,
ttl: 10 * 1000, // convert to ms
updateAgeOnGet: true,
});
}
describe('Use within GenericEntityCacheAdapter', () => {
describe('loadManyAsync', () => {
it('returns appropriate cache results', async () => {
const cacheAdapter = new GenericEntityCacheAdapter(
new GenericLocalMemoryCacher(entityConfiguration, createTTLCache()),
);
const cacheHits = new Map<SingleFieldValueHolder<BlahFields, 'id'>, Readonly<BlahFields>>([
[new SingleFieldValueHolder('test-id-1'), { id: 'test-id-1' }],
]);
await cacheAdapter.cacheManyAsync(
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
cacheHits,
);
await cacheAdapter.cacheDBMissesAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-2'),
]);
const results = await cacheAdapter.loadManyAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-1'),
new SingleFieldValueHolder('test-id-2'),
new SingleFieldValueHolder('test-id-3'),
]);
expect(results.get(new SingleFieldValueHolder('test-id-1'))).toMatchObject({
status: CacheStatus.HIT,
item: { id: 'test-id-1' },
});
expect(results.get(new SingleFieldValueHolder('test-id-2'))).toMatchObject({
status: CacheStatus.NEGATIVE,
});
expect(results.get(new SingleFieldValueHolder('test-id-3'))).toMatchObject({
status: CacheStatus.MISS,
});
expect(results.size).toBe(3);
});
it('respects specified options', () => {
const cache = GenericLocalMemoryCacher.createLRUCache({
ttlSeconds: 3,
maxSize: 10,
});
expect(cache.max).toBe(10);
expect(cache.maxAge).toBe(3000);
it('returns empty map when passed empty array of values', async () => {
const cacheAdapter = new GenericEntityCacheAdapter(
new GenericLocalMemoryCacher(entityConfiguration, createTTLCache()),
);
const results = await cacheAdapter.loadManyAsync(
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
[] as SingleFieldValueHolder<BlahFields, 'id'>[],
);
expect(results).toEqual(new SingleFieldValueHolderMap(new Map()));
});
});
describe(GenericLocalMemoryCacher.createNoOpCache, () => {
it('creates a no-op cache', () => {
const cache = GenericLocalMemoryCacher.createNoOpCache<{ hello: 'world' }>();
cache.set('a', { hello: 'world' });
expect(cache.get('a')).toBeUndefined();
describe('cacheManyAsync', () => {
it('correctly caches all objects', async () => {
const localMemoryCache = createTTLCache<BlahFields>();
const localMemoryCacher = new GenericLocalMemoryCacher(entityConfiguration, localMemoryCache);
const cacheAdapter = new GenericEntityCacheAdapter(localMemoryCacher);
await cacheAdapter.cacheManyAsync(
new SingleFieldHolder('id'),
new Map([[new SingleFieldValueHolder('test-id-1'), { id: 'test-id-1' }]]),
);
const cacheKey = localMemoryCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('test-id-1'),
);
expect(localMemoryCache.get(cacheKey)).toMatchObject({
id: 'test-id-1',
});
});
});
describe('cacheDBMissesAsync', () => {
it('correctly caches misses', async () => {
const localMemoryCache = createTTLCache<BlahFields>();
const localMemoryCacher = new GenericLocalMemoryCacher(entityConfiguration, localMemoryCache);
const cacheAdapter = new GenericEntityCacheAdapter(localMemoryCacher);
await cacheAdapter.cacheDBMissesAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-1'),
]);
const cacheKey = localMemoryCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('test-id-1'),
);
expect(localMemoryCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
});
});
describe('invalidateManyAsync', () => {
it('invalidates correctly', async () => {
const localMemoryCache = createTTLCache<BlahFields>();
const cacheAdapter = new GenericEntityCacheAdapter(
new GenericLocalMemoryCacher(entityConfiguration, localMemoryCache),
);
await cacheAdapter.cacheManyAsync(
new SingleFieldHolder('id'),
new Map([[new SingleFieldValueHolder('test-id-1'), { id: 'test-id-1' }]]),
);
await cacheAdapter.cacheDBMissesAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-2'),
]);
await cacheAdapter.invalidateManyAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-1'),
new SingleFieldValueHolder('test-id-2'),
]);
const results = await cacheAdapter.loadManyAsync(new SingleFieldHolder('id'), [
new SingleFieldValueHolder('test-id-1'),
new SingleFieldValueHolder('test-id-2'),
]);
expect(results.get(new SingleFieldValueHolder('test-id-1'))).toMatchObject({
status: CacheStatus.MISS,
});
expect(results.get(new SingleFieldValueHolder('test-id-2'))).toMatchObject({
status: CacheStatus.MISS,
});
});
it('returns when passed empty array of values', async () => {
const cacheAdapter = new GenericEntityCacheAdapter(
new GenericLocalMemoryCacher(entityConfiguration, createTTLCache<BlahFields>()),
);
await cacheAdapter.invalidateManyAsync(
new SingleFieldHolder<BlahFields, 'id', 'id'>('id'),
[] as SingleFieldValueHolder<BlahFields, 'id'>[],
);
});
});
});
@@ -1,174 +0,0 @@
import { CacheStatus, ViewerContext } from '@expo/entity';
import { v4 as uuidv4 } from 'uuid';
import GenericLocalMemoryCacher from '../GenericLocalMemoryCacher';
import LocalMemoryCacheAdapter from '../LocalMemoryCacheAdapter';
import LocalMemoryCacheAdapterProvider from '../LocalMemoryCacheAdapterProvider';
import LocalMemoryTestEntity from '../testfixtures/LocalMemoryTestEntity';
import {
createLocalMemoryTestEntityCompanionProvider,
createNoopLocalMemoryIntegrationTestEntityCompanionProvider,
} from '../testfixtures/createLocalMemoryTestEntityCompanionProvider';
class TestViewerContext extends ViewerContext {}
describe(LocalMemoryCacheAdapter, () => {
beforeEach(async () => {
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.clear();
});
it('has correct caching behavior', async () => {
const viewerContext = new TestViewerContext(createLocalMemoryTestEntityCompanionProvider());
const cacheAdapter = viewerContext.entityCompanionProvider.getCompanionForEntity(
LocalMemoryTestEntity,
LocalMemoryTestEntity.getCompanionDefinition()
)['tableDataCoordinator']['cacheAdapter'];
const cacheKeyMaker = cacheAdapter['makeCacheKey'].bind(cacheAdapter);
const date = new Date();
const entity1Created = await LocalMemoryTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.enforceCreateAsync();
// loading an entity should put it in cache
const entity1 = await LocalMemoryTestEntity.loader(viewerContext)
.enforcing()
.loadByIDAsync(entity1Created.getID());
const entitySpecificGenericCacher =
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
)!['genericLocalMemoryCacher'];
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker('id', entity1.getID()),
]);
const cachedValue = cachedResult.get(cacheKeyMaker('id', entity1.getID()))!;
expect(cachedValue).toMatchObject({
status: CacheStatus.HIT,
item: {
id: entity1.getID(),
name: 'blah',
dateField: date,
},
});
// simulate non existent db fetch, should write negative result ('') to cache
const nonExistentId = uuidv4();
const entityNonExistentResult = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
nonExistentId
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker('id', nonExistentId),
]);
expect(nonExistentCachedResult.get(cacheKeyMaker('id', nonExistentId))).toMatchObject({
status: CacheStatus.NEGATIVE,
});
// load again through entities framework to ensure it reads negative result
const entityNonExistentResult2 = await LocalMemoryTestEntity.loader(
viewerContext
).loadByIDAsync(nonExistentId);
expect(entityNonExistentResult2.ok).toBe(false);
// invalidate from cache to ensure it invalidates correctly
await LocalMemoryTestEntity.loader(viewerContext).invalidateFieldsAsync(entity1.getAllFields());
const cachedResultMiss = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker('id', entity1.getID()),
]);
const cachedValueMiss = cachedResultMiss.get(cacheKeyMaker('id', entity1.getID()));
expect(cachedValueMiss).toMatchObject({ status: CacheStatus.MISS });
});
it('shares the cache between different requests', async () => {
const genericLocalMemoryCacherLoadManySpy = jest.spyOn(
GenericLocalMemoryCacher.prototype as unknown as any,
'loadManyAsync'
);
const viewerContext = new TestViewerContext(createLocalMemoryTestEntityCompanionProvider());
const date = new Date();
const entity1Created = await LocalMemoryTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.enforceCreateAsync();
// loading an entity should put it in cache
await LocalMemoryTestEntity.loader(viewerContext)
.enforcing()
.loadByIDAsync(entity1Created.getID());
// load entity with a different request
const viewerContext2 = new TestViewerContext(createLocalMemoryTestEntityCompanionProvider());
const entity1WithVc2 = await LocalMemoryTestEntity.loader(viewerContext2)
.enforcing()
.loadByIDAsync(entity1Created.getID());
const cacheAdapter = viewerContext.entityCompanionProvider.getCompanionForEntity(
LocalMemoryTestEntity,
LocalMemoryTestEntity.getCompanionDefinition()
)['tableDataCoordinator']['cacheAdapter'];
const cacheKeyMaker = cacheAdapter['makeCacheKey'].bind(cacheAdapter);
expect(entity1WithVc2.getAllFields()).toMatchObject({
id: entity1WithVc2.getID(),
name: 'blah',
dateField: date,
});
expect(genericLocalMemoryCacherLoadManySpy).toBeCalledWith([
cacheKeyMaker('id', entity1WithVc2.getID()),
]);
expect(genericLocalMemoryCacherLoadManySpy).toBeCalledTimes(2);
});
it('respects the parameters of a noop cache', async () => {
const viewerContext = new TestViewerContext(
createNoopLocalMemoryIntegrationTestEntityCompanionProvider()
);
const cacheAdapter = viewerContext.entityCompanionProvider.getCompanionForEntity(
LocalMemoryTestEntity,
LocalMemoryTestEntity.getCompanionDefinition()
)['tableDataCoordinator']['cacheAdapter'];
const cacheKeyMaker = cacheAdapter['makeCacheKey'].bind(cacheAdapter);
const date = new Date();
const entity1Created = await LocalMemoryTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.enforceCreateAsync();
// loading an entity will try to put it in cache but it's a noop cache, so it should be a miss
const entity1 = await LocalMemoryTestEntity.loader(viewerContext)
.enforcing()
.loadByIDAsync(entity1Created.getID());
const entitySpecificGenericCacher =
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
)!['genericLocalMemoryCacher'];
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker('id', entity1.getID()),
]);
const cachedValue = cachedResult.get(cacheKeyMaker('id', entity1.getID()))!;
expect(cachedValue).toMatchObject({
status: CacheStatus.MISS,
});
// a non existent db fetch should try to write negative result ('') but it's a noop cache, so it should be a miss
const nonExistentId = uuidv4();
const entityNonExistentResult = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
nonExistentId
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedResult = await entitySpecificGenericCacher.loadManyAsync([
cacheKeyMaker('id', nonExistentId),
]);
expect(nonExistentCachedResult.get(cacheKeyMaker('id', nonExistentId))).toMatchObject({
status: CacheStatus.MISS,
});
});
});
@@ -1,107 +0,0 @@
import { CacheStatus, UUIDField, EntityConfiguration } from '@expo/entity';
import GenericLocalMemoryCacher, {
DOES_NOT_EXIST_LOCAL_MEMORY_CACHE,
} from '../GenericLocalMemoryCacher';
import LocalMemoryCacheAdapter from '../LocalMemoryCacheAdapter';
type BlahFields = {
id: string;
};
const entityConfiguration = new EntityConfiguration<BlahFields>({
idField: 'id',
tableName: 'blah',
schema: {
id: new UUIDField({ columnName: 'id', cache: true }),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'local-memory',
});
describe(LocalMemoryCacheAdapter, () => {
describe('loadManyAsync', () => {
it('returns appropriate cache results', async () => {
const cacheAdapter = new LocalMemoryCacheAdapter(
entityConfiguration,
GenericLocalMemoryCacher.createLRUCache()
);
const cacheHits = new Map<string, Readonly<BlahFields>>([['test-id-1', { id: 'test-id-1' }]]);
await cacheAdapter.cacheManyAsync('id', cacheHits);
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
const results = await cacheAdapter.loadManyAsync('id', [
'test-id-1',
'test-id-2',
'test-id-3',
]);
expect(results.get('test-id-1')).toMatchObject({
status: CacheStatus.HIT,
item: { id: 'test-id-1' },
});
expect(results.get('test-id-2')).toMatchObject({ status: CacheStatus.NEGATIVE });
expect(results.get('test-id-3')).toMatchObject({ status: CacheStatus.MISS });
expect(results.size).toBe(3);
});
it('returns empty map when passed empty array of fieldValues', async () => {
const cacheAdapter = new LocalMemoryCacheAdapter(
entityConfiguration,
GenericLocalMemoryCacher.createLRUCache()
);
const results = await cacheAdapter.loadManyAsync('id', []);
expect(results).toEqual(new Map());
});
});
describe('cacheManyAsync', () => {
it('correctly caches all objects', async () => {
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
expect(localMemoryCache.get(cacheKey)).toMatchObject({
id: 'test-id-1',
});
});
});
describe('cacheDBMissesAsync', () => {
it('correctly caches misses', async () => {
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-1']);
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
expect(localMemoryCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
});
});
describe('invalidateManyAsync', () => {
it('invalidates correctly', async () => {
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
await cacheAdapter.invalidateManyAsync('id', ['test-id-1', 'test-id-2']);
const results = await cacheAdapter.loadManyAsync('id', ['test-id-1', 'test-id-2']);
expect(results.get('test-id-1')).toMatchObject({ status: CacheStatus.MISS });
expect(results.get('test-id-2')).toMatchObject({ status: CacheStatus.MISS });
});
it('returns when passed empty array of fieldValues', async () => {
const cacheAdapter = new LocalMemoryCacheAdapter(
entityConfiguration,
GenericLocalMemoryCacher.createLRUCache<BlahFields>({})
);
await cacheAdapter.invalidateManyAsync('id', []);
});
});
});
@@ -4,7 +4,5 @@
* @module @expo/entity-cache-adapter-local-memory
*/
export { default as GenericLocalMemoryCacher } from './GenericLocalMemoryCacher';
export * from './GenericLocalMemoryCacher';
export { default as LocalMemoryCacheAdapter } from './LocalMemoryCacheAdapter';
export { default as LocalMemoryCacheAdapterProvider } from './LocalMemoryCacheAdapterProvider';
export * from './GenericLocalMemoryCacher.ts';
export * from './LocalMemoryCacheAdapterProvider.ts';
@@ -1,48 +0,0 @@
import {
NoOpEntityMetricsAdapter,
IEntityMetricsAdapter,
EntityCompanionProvider,
StubQueryContextProvider,
StubDatabaseAdapterProvider,
} from '@expo/entity';
import LocalMemoryCacheAdapterProvider from '../LocalMemoryCacheAdapterProvider';
export const createLocalMemoryTestEntityCompanionProvider = (
localMemoryOptions: { maxSize?: number; ttlSeconds?: number } = {},
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter()
): EntityCompanionProvider => {
const localMemoryCacheAdapterProvider =
localMemoryOptions.maxSize === 0 && localMemoryOptions.ttlSeconds === 0
? LocalMemoryCacheAdapterProvider.getNoOpProvider()
: LocalMemoryCacheAdapterProvider.getProvider(localMemoryOptions);
return new EntityCompanionProvider(
metricsAdapter,
new Map([
[
'postgres',
{
adapterProvider: new StubDatabaseAdapterProvider(),
queryContextProvider: StubQueryContextProvider,
},
],
]),
new Map([
[
'local-memory',
{
cacheAdapterProvider: localMemoryCacheAdapterProvider,
},
],
])
);
};
export const createNoopLocalMemoryIntegrationTestEntityCompanionProvider = (
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter()
): EntityCompanionProvider => {
return createLocalMemoryTestEntityCompanionProvider(
{ maxSize: 0, ttlSeconds: 0 },
metricsAdapter
);
};
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "build",
},
"include": ["src"]
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"],
"references": [{ "path": "../entity" }, { "path": "../entity-testing-utils" }]
}
+216 -148
View File
@@ -3,301 +3,369 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
### Bug Fixes
- escape all matching portions of parts of cacher cache keys ([#486](https://github.com/expo/entity/issues/486)) ([21f2f6f](https://github.com/expo/entity/commit/21f2f6f461ee771e8b1c1b22dbaf9b275094c0db))
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
### Bug Fixes
- eliminate some unnecessary any casts ([#448](https://github.com/expo/entity/issues/448)) ([cc78029](https://github.com/expo/entity/commit/cc78029117c14510b0934b5e0d19876a45570f8d))
# [0.55.0](https://github.com/expo/entity/compare/v0.54.0...v0.55.0) (2026-01-27)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.54.0](https://github.com/expo/entity/compare/v0.53.0...v0.54.0) (2026-01-21)
### Bug Fixes
- **deps:** pin dependencies ([#328](https://github.com/expo/entity/issues/328)) ([5679f27](https://github.com/expo/entity/commit/5679f27209f1515ca5e626d858c4aa054b414625))
# [0.53.0](https://github.com/expo/entity/compare/v0.52.0...v0.53.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.52.0](https://github.com/expo/entity/compare/v0.51.0...v0.52.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.51.0](https://github.com/expo/entity/compare/v0.50.0...v0.51.0) (2025-11-21)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.50.0](https://github.com/expo/entity/compare/v0.49.0...v0.50.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.49.0](https://github.com/expo/entity/compare/v0.48.0...v0.49.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.48.0](https://github.com/expo/entity/compare/v0.47.0...v0.48.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.47.0](https://github.com/expo/entity/compare/v0.46.0...v0.47.0) (2025-09-19)
### Features
- add support for Buffer fields ([#305](https://github.com/expo/entity/issues/305)) ([31b52a5](https://github.com/expo/entity/commit/31b52a5f75842f4810f66c12ad03a10d9192f889))
# [0.46.0](https://github.com/expo/entity/compare/v0.45.0...v0.46.0) (2025-06-26)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.45.0](https://github.com/expo/entity/compare/v0.44.0...v0.45.0) (2025-06-06)
### Features
- add ability to define a custom cache key invalidation version supplier ([#290](https://github.com/expo/entity/issues/290)) ([a3ab61b](https://github.com/expo/entity/commit/a3ab61bfa8d2eab3bbd9c703c5e483184bb67f13))
# [0.44.0](https://github.com/expo/entity/compare/v0.43.0...v0.44.0) (2025-05-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.43.0](https://github.com/expo/entity/compare/v0.42.0...v0.43.0) (2025-04-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.42.0](https://github.com/expo/entity/compare/v0.41.0...v0.42.0) (2025-04-10)
### Features
- add @expo/entity-testing-utils package ([#280](https://github.com/expo/entity/issues/280)) ([485894a](https://github.com/expo/entity/commit/485894af16e233d533a9480285bbda56812cbe0b))
- add composite field loading and caching ([#272](https://github.com/expo/entity/issues/272)) ([f0aa0da](https://github.com/expo/entity/commit/f0aa0dafebdb56418cbd22dda437233d850db3e7))
- change entity id field generic to field name and derive type where necessary ([#278](https://github.com/expo/entity/issues/278)) ([b7e524c](https://github.com/expo/entity/commit/b7e524c41892797608d0b884410b8d520a80a9ef))
- convert batched/cached loader interface to holder pattern ([#271](https://github.com/expo/entity/issues/271)) ([06b3cb7](https://github.com/expo/entity/commit/06b3cb7f8076ef9e5b83cdf154da159801bd14cc)), closes [#201](https://github.com/expo/entity/issues/201)
- invalidate n+/-1 cacheKeyVersion for entities for push safety ([#275](https://github.com/expo/entity/issues/275)) ([d9c1852](https://github.com/expo/entity/commit/d9c1852706f98f0761ff24ddd244e4ed2beca580))
# [0.41.0](https://github.com/expo/entity/compare/v0.40.0...v0.41.0) (2025-03-07)
- feat!: Default all loaders and mutators to enforcing (#256) ([a976e76](https://github.com/expo/entity/commit/a976e765098bca27049585e43a3bed5c71b9ac6b)), closes [#256](https://github.com/expo/entity/issues/256)
### BREAKING CHANGES
- Default all loaders and mutators to enforcing
# [0.40.0](https://github.com/expo/entity/compare/v0.39.0...v0.40.0) (2025-02-27)
### Reverts
- Revert "v0.40.0" ([fa78ce8](https://github.com/expo/entity/commit/fa78ce84e2f2eb8199e36fd51779fc68905001c0))
# [0.39.0](https://github.com/expo/entity/compare/v0.38.0...v0.39.0) (2024-11-21)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.38.0](https://github.com/expo/entity/compare/v0.37.0...v0.38.0) (2024-06-27)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.37.0](https://github.com/expo/entity/compare/v0.36.0...v0.37.0) (2024-06-12)
### Bug Fixes
- update barrels ([#241](https://github.com/expo/entity/issues/241)) ([8c5f81b](https://github.com/expo/entity/commit/8c5f81bcca107e9b7bbea5f0ab41b0105057806e))
# [0.36.0](https://github.com/expo/entity/compare/v0.35.0...v0.36.0) (2024-06-12)
### Bug Fixes
- constrain entity fields type to string-keyed object ([#235](https://github.com/expo/entity/issues/235)) ([7e2cea1](https://github.com/expo/entity/commit/7e2cea16973a0ae1917f867cd25d6ef7c8eaecef))
# [0.35.0](https://github.com/expo/entity/compare/v0.34.0...v0.35.0) (2024-04-11)
### Bug Fixes
- Revert: require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([1bdfbd6](https://github.com/expo/entity/commit/1bdfbd6b562d1200e4029df0533b5adb2d917831))
# [0.34.0](https://github.com/expo/entity/compare/v0.33.0...v0.34.0) (2024-04-11)
### Features
- require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([8b0b31f](https://github.com/expo/entity/commit/8b0b31fdde5bd565aa527719003ef283a45f55cc))
# [0.33.0](https://github.com/expo/entity/compare/v0.32.0...v0.33.0) (2023-10-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.32.0](https://github.com/expo/entity/compare/v0.31.1...v0.32.0) (2023-06-10)
### Bug Fixes
- remove unused field from redis configuration ([#213](https://github.com/expo/entity/issues/213)) ([503a2fb](https://github.com/expo/entity/commit/503a2fb6a8d57f6a09f6da99daf5ab92fc3e7945))
### Features
- entity companion definition static thunk ([#210](https://github.com/expo/entity/issues/210)) ([4b18010](https://github.com/expo/entity/commit/4b18010d42be50ef329f428b08330e21bf676586))
# [0.31.0](https://github.com/expo/entity/compare/v0.30.0...v0.31.0) (2023-01-19)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.30.0](https://github.com/expo/entity/compare/v0.29.0...v0.30.0) (2022-12-23)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.29.0](https://github.com/expo/entity/compare/v0.28.0...v0.29.0) (2022-10-06)
### Features
- add example of redis mget batching using @expo/batcher ([#199](https://github.com/expo/entity/issues/199)) ([684f91b](https://github.com/expo/entity/commit/684f91b1f270851dbe331df2e2620664eea17266))
- change redis cache adapter to use redis-like interface ([#200](https://github.com/expo/entity/issues/200)) ([e381f5b](https://github.com/expo/entity/commit/e381f5b2acfc17dcade2cf6e8506fa1c1c255e73))
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.27.0](https://github.com/expo/entity/compare/v0.26.1...v0.27.0) (2022-08-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.26.1](https://github.com/expo/entity/compare/v0.26.0...v0.26.1) (2022-07-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.26.0](https://github.com/expo/entity/compare/v0.25.3...v0.26.0) (2022-07-14)
### Features
* update ioredis to v5, make it a devDependency of the cache packages ([#172](https://github.com/expo/entity/issues/172)) ([9fcfbf7](https://github.com/expo/entity/commit/9fcfbf746a61554cefedf7fb0f1a3a9056b58ffe))
- update ioredis to v5, make it a devDependency of the cache packages ([#172](https://github.com/expo/entity/issues/172)) ([9fcfbf7](https://github.com/expo/entity/commit/9fcfbf746a61554cefedf7fb0f1a3a9056b58ffe))
## [0.25.3](https://github.com/expo/entity/compare/v0.25.2...v0.25.3) (2022-03-12)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.25.2](https://github.com/expo/entity/compare/v0.25.1...v0.25.2) (2022-03-12)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.25.1](https://github.com/expo/entity/compare/v0.25.0...v0.25.1) (2022-03-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.25.0](https://github.com/expo/entity/compare/v0.24.0...v0.25.0) (2022-03-10)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.24.0](https://github.com/expo/entity/compare/v0.23.0...v0.24.0) (2022-02-16)
### Chores
* Refactor cachers to use an abstract class and interface ([#157](https://github.com/expo/entity/pull/157))
- Refactor cachers to use an abstract class and interface ([#157](https://github.com/expo/entity/pull/157))
# [0.23.0](https://github.com/expo/entity/compare/v0.22.0...v0.23.0) (2022-02-09)
### Chores
* Move data transforming responsibilities to the CacheAdapter instead of the ReadThroughEntityCache. ([#153](https://github.com/expo/entity/pull/153))
- Move data transforming responsibilities to the CacheAdapter instead of the ReadThroughEntityCache. ([#153](https://github.com/expo/entity/pull/153))
# [0.22.0](https://github.com/expo/entity/compare/v0.21.0...v0.22.0) (2022-02-04)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.21.0](https://github.com/expo/entity/compare/v0.20.0...v0.21.0) (2022-01-03)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.20.0](https://github.com/expo/entity/compare/v0.19.0...v0.20.0) (2021-12-29)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.19.0](https://github.com/expo/entity/compare/v0.18.0...v0.19.0) (2021-10-28)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.18.0](https://github.com/expo/entity/compare/v0.17.0...v0.18.0) (2021-10-14)
### Features
* enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
* upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
- enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
- upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
# [0.17.0](https://github.com/expo/entity/compare/v0.16.0...v0.17.0) (2021-08-16)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.16.0](https://github.com/expo/entity/compare/v0.15.0...v0.16.0) (2021-07-07)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.15.0](https://github.com/expo/entity/compare/v0.14.1...v0.15.0) (2021-05-26)
### Bug Fixes
* upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
* use column name instead of field name for redis cache key ([#124](https://github.com/expo/entity/issues/124)) ([d78f452](https://github.com/expo/entity/commit/d78f452bb3ac5527069813a03f5b2264375bd8ab))
- upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
- use column name instead of field name for redis cache key ([#124](https://github.com/expo/entity/issues/124)) ([d78f452](https://github.com/expo/entity/commit/d78f452bb3ac5527069813a03f5b2264375bd8ab))
### Features
* secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
- secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
## [0.14.1](https://github.com/expo/entity/compare/v0.14.0...v0.14.1) (2021-03-24)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.14.0](https://github.com/expo/entity/compare/v0.13.0...v0.14.0) (2021-03-24)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.13.0](https://github.com/expo/entity/compare/v0.12.0...v0.13.0) (2021-02-12)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.12.0](https://github.com/expo/entity/compare/v0.11.0...v0.12.0) (2021-01-22)
### Bug Fixes
* remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
- remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
# [0.11.0](https://github.com/expo/entity/compare/v0.10.0...v0.11.0) (2020-12-24)
### Features
* better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
- better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.9.0](https://github.com/expo/entity/compare/v0.8.1...v0.9.0) (2020-11-05)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.8.1](https://github.com/expo/entity/compare/v0.8.0...v0.8.1) (2020-10-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.8.0](https://github.com/expo/entity/compare/v0.7.1...v0.8.0) (2020-10-09)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.7.1](https://github.com/expo/entity/compare/v0.7.0...v0.7.1) (2020-10-01)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# [0.7.0](https://github.com/expo/entity/compare/v0.6.0...v0.7.0) (2020-10-01)
### Bug Fixes
* upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
- upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
# [0.6.0](https://github.com/expo/entity/compare/v0.5.2...v0.6.0) (2020-07-23)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.5.2](https://github.com/expo/entity/compare/v0.5.1...v0.5.2) (2020-07-02)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
## [0.5.1](https://github.com/expo/entity/compare/v0.5.0...v0.5.1) (2020-07-01)
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
# 0.5.0 (2020-07-01)
### Features
* support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
- support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
+4 -11
View File
@@ -11,17 +11,10 @@ During `EntityCompanionProvider` instantiation:
```typescript
import Redis from 'ioredis';
const redisCacheAdapterContext = {
const genericRedisCacherContext = {
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
makeKeyFn(...parts: string[]): string {
const delimiter = ':';
const escapedParts = parts.map((part) =>
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
);
return escapedParts.join(delimiter);
},
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'ent-',
cacheKeyVersion: 1,
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
};
@@ -36,9 +29,9 @@ export const createDefaultEntityCompanionProvider = (
},
{
['redis']: {
cacheAdapterProvider: new RedisCacheAdapterProvider(redisCacheAdapterContext),
cacheAdapterProvider: new RedisCacheAdapterProvider(genericRedisCacheContext),
},
}
);
};
```
```
@@ -1,37 +1,45 @@
{
"name": "@expo/entity-cache-adapter-redis",
"version": "0.28.0",
"version": "0.72.0",
"description": "Redis cache adapter for @expo/entity",
"files": [
"build",
"src"
],
"main": "build/index.js",
"types": "build/index.d.ts",
"scripts": {
"tsc": "tsc",
"clean": "rm -rf build coverage coverage-integration",
"lint": "eslint src",
"lint-fix": "eslint src --fix",
"test": "jest --rootDir . --config ../../resources/jest.config.js",
"integration": "../../resources/run-with-docker yarn integration-no-setup",
"integration-no-setup": "jest --config ../../resources/jest-integration.config.js --rootDir . --runInBand --passWithNoTests",
"barrelsby": "barrelsby --directory src --location top --exclude tests__ --singleQuotes --exportDefault --delete"
},
"engines": {
"node": ">=12"
},
"keywords": [
"entity"
],
"author": "Expo",
"license": "MIT",
"peerDependencies": {
"@expo/entity": "*",
"ioredis": ">=5"
"author": "Expo",
"files": [
"build",
"!*.tsbuildinfo",
"!__*",
"src"
],
"type": "module",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"scripts": {
"build": "tsc --build",
"prepack": "rm -rf build && yarn build",
"clean": "yarn build --clean",
"lint": "yarn run --top-level oxlint --type-aware src",
"lint-fix": "yarn lint --fix",
"test": "yarn test:all --rootDir $(pwd)",
"integration": "yarn integration:all --rootDir $(pwd)"
},
"dependencies": {
"@expo/entity": "workspace:^"
},
"devDependencies": {
"@expo/entity": "^0.28.0",
"ioredis": "^5.0.4"
"@expo/batcher": "1.1.2",
"@expo/entity-testing-utils": "workspace:^",
"@jest/globals": "30.5.2",
"ioredis": "5.11.1",
"ts-mockito": "2.6.1",
"typescript": "6.0.3"
},
"peerDependencies": {
"ioredis": ">=5"
},
"engines": {
"node": ">=18"
}
}
@@ -1,25 +1,127 @@
import {
import type {
CacheLoadResult,
CacheStatus,
EntityConfiguration,
IEntityGenericCacher,
IEntityLoadKey,
IEntityLoadValue,
} from '@expo/entity';
import {
CacheStatus,
transformCacheObjectToFields,
transformFieldsToCacheObject,
IEntityGenericCacher,
} from '@expo/entity';
import { Redis } from 'ioredis';
import { redisTransformerMap } from './RedisCommon';
import wrapNativeRedisCallAsync from './errors/wrapNativeRedisCallAsync';
import { redisTransformerMap } from './RedisCommon.ts';
import { wrapNativeRedisCallAsync } from './errors/wrapNativeRedisCallAsync.ts';
import { getSurroundingCacheKeyVersionsForInvalidation } from './utils/getSurroundingCacheKeyVersionsForInvalidation.ts';
// Sentinel value we store in Redis to negatively cache a database miss.
// The sentinel value is distinct from any (positively) cached value.
const DOES_NOT_EXIST_REDIS = '';
export interface IRedisTransaction {
set(key: string, value: string, secondsToken: 'EX', seconds: number): this;
exec(): Promise<any>;
}
export interface IRedis {
mget(...args: [...keys: string[]]): Promise<(string | null)[]>;
multi(): IRedisTransaction;
del(...args: [...keys: string[]]): Promise<any>;
}
/**
* The strategy for generating the set of cache keys to invalidate in the Redis cache after entity mutation.
*/
export enum RedisCacheInvalidationStrategy {
/**
* Invalidate just the cache key(s) for the current cacheKeyVersion of the entity.
*/
CURRENT_CACHE_KEY_VERSION = 'current-cache-key-version',
/**
* Invalidate the cache key(s) for the current cacheKeyVersion and the surrounding cache key versions
* (e.g. `1`, `2` and `3` if the current version is `2`). This can be useful for deployment safety, where
* some machines may be operating on an old version of the code and thus an old cacheKeyVersion, and some the new version.
* This strategy generates cache keys for both old and potential future new versions.
*/
SURROUNDING_CACHE_KEY_VERSIONS = 'surrounding-cache-key-versions',
/**
* Invalidate cache keys based on user-specified function from the current cacheKeyVersion to a list of cache key
* versions to invalidate.
*/
CUSTOM = 'custom',
}
export type GenericRedisCacheInvalidationConfig =
| {
/**
* Invalidation strategy for the cache.
*/
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION;
}
| {
/**
* Invalidation strategy for the cache.
*/
invalidationStrategy: RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS;
}
| {
/**
* Invalidation strategy for the cache.
*/
invalidationStrategy: RedisCacheInvalidationStrategy.CUSTOM;
/**
* Function that takes the current cache key version and returns the cache key versions to invalidate.
*/
cacheKeyVersionsToInvalidateFn: (cacheKeyVersion: number) => readonly number[];
};
function validateGenericRedisCacheContext(context: GenericRedisCacheContext): void {
if (context.cacheKeyDelimiter.length === 0) {
throw new Error('GenericRedisCacheContext.cacheKeyDelimiter must be a non-empty string');
}
if (context.cacheKeyDelimiter.includes('\\')) {
throw new Error(
`GenericRedisCacheContext.cacheKeyDelimiter must not contain the escape character "\\" (got ${JSON.stringify(context.cacheKeyDelimiter)})`,
);
}
if (!Number.isInteger(context.ttlSecondsPositive) || context.ttlSecondsPositive <= 0) {
throw new Error(
`GenericRedisCacheContext.ttlSecondsPositive must be a positive integer (got ${context.ttlSecondsPositive})`,
);
}
if (!Number.isInteger(context.ttlSecondsNegative) || context.ttlSecondsNegative <= 0) {
throw new Error(
`GenericRedisCacheContext.ttlSecondsNegative must be a positive integer (got ${context.ttlSecondsNegative})`,
);
}
}
export interface GenericRedisCacheContext {
/**
* Instance of ioredis.Redis
*/
redisClient: Redis;
redisClient: IRedis;
/**
* Delimiter used to join the parts of a Redis cache key (cache key prefix,
* versions, entity name, field values, etc). Typically `:`.
*
* The cacher escapes occurrences of this delimiter (and the escape character `\`)
* within each part before joining, so the encoding is injective regardless of
* what the parts contain. This prevents a value that happens to contain the
* delimiter from colliding with a different (key, value) pair.
*/
cacheKeyDelimiter: string;
/**
* Prefix prepended to all entity cache keys. Useful for adding a short, human-readable
* distintion for entity keys, e.g. `ent-`
*/
cacheKeyPrefix: string;
/**
* TTL for caching database hits. Successive entity loads within this TTL
@@ -32,23 +134,33 @@ export interface GenericRedisCacheContext {
* this TTL will be assumed not present in the database (unless invalidated).
*/
ttlSecondsNegative: number;
/**
* Configuration for cache invalidation strategy.
*/
invalidationConfig: GenericRedisCacheInvalidationConfig;
}
export default class GenericRedisCacher<TFields> implements IEntityGenericCacher<TFields> {
export class GenericRedisCacher<
TFields extends Record<string, any>,
TIDField extends keyof TFields,
> implements IEntityGenericCacher<TFields, TIDField> {
constructor(
private readonly context: GenericRedisCacheContext,
private readonly entityConfiguration: EntityConfiguration<TFields>
) {}
private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
) {
validateGenericRedisCacheContext(context);
}
public async loadManyAsync(
keys: readonly string[]
keys: readonly string[],
): Promise<ReadonlyMap<string, CacheLoadResult<TFields>>> {
if (keys.length === 0) {
return new Map();
}
const redisResults = await wrapNativeRedisCallAsync(() =>
this.context.redisClient.mget(...keys)
this.context.redisClient.mget(...keys),
);
const results = new Map<string, CacheLoadResult<TFields>>();
@@ -66,7 +178,7 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
item: transformCacheObjectToFields(
this.entityConfiguration,
redisTransformerMap,
JSON.parse(redisResult)
JSON.parse(redisResult),
),
});
} else {
@@ -88,10 +200,10 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
redisTransaction = redisTransaction.set(
key,
JSON.stringify(
transformFieldsToCacheObject(this.entityConfiguration, redisTransformerMap, object)
transformFieldsToCacheObject(this.entityConfiguration, redisTransformerMap, object),
),
'EX',
this.context.ttlSecondsPositive
this.context.ttlSecondsPositive,
);
});
await wrapNativeRedisCallAsync(() => redisTransaction.exec());
@@ -108,7 +220,7 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
key,
DOES_NOT_EXIST_REDIS,
'EX',
this.context.ttlSecondsNegative
this.context.ttlSecondsNegative,
);
});
await wrapNativeRedisCallAsync(() => redisTransaction.exec());
@@ -121,4 +233,68 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
await wrapNativeRedisCallAsync(() => this.context.redisClient.del(...keys));
}
private makeCacheKeyForCacheKeyVersion<
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
TSerializedLoadValue,
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
>(key: TLoadKey, value: TLoadValue, cacheKeyVersion: number): string {
const cacheKeyType = key.getLoadMethodType();
const keyAndValueParts = key.createCacheKeyPartsForLoadValue(this.entityConfiguration, value);
const allParts = [
this.context.cacheKeyPrefix,
cacheKeyType,
this.entityConfiguration.tableName,
`v4.${cacheKeyVersion}`,
...keyAndValueParts,
];
const delimiter = this.context.cacheKeyDelimiter;
return allParts
.map((part) => part.replaceAll('\\', '\\\\').replaceAll(delimiter, `\\${delimiter}`))
.join(delimiter);
}
public makeCacheKeyForStorage<
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
TSerializedLoadValue,
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
>(key: TLoadKey, value: TLoadValue): string {
return this.makeCacheKeyForCacheKeyVersion(
key,
value,
this.entityConfiguration.cacheKeyVersion,
);
}
public makeCacheKeysForInvalidation<
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
TSerializedLoadValue,
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
>(key: TLoadKey, value: TLoadValue): readonly string[] {
switch (this.context.invalidationConfig.invalidationStrategy) {
case RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION:
return [
this.makeCacheKeyForCacheKeyVersion(key, value, this.entityConfiguration.cacheKeyVersion),
];
case RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS:
return getSurroundingCacheKeyVersionsForInvalidation(
this.entityConfiguration.cacheKeyVersion,
).map((cacheKeyVersion) =>
this.makeCacheKeyForCacheKeyVersion(key, value, cacheKeyVersion),
);
case RedisCacheInvalidationStrategy.CUSTOM: {
const cacheKeyVersions = this.context.invalidationConfig.cacheKeyVersionsToInvalidateFn(
this.entityConfiguration.cacheKeyVersion,
);
if (cacheKeyVersions.length === 0) {
throw new Error(
`GenericRedisCacheContext.invalidationConfig.cacheKeyVersionsToInvalidateFn returned an empty list for cacheKeyVersion ${this.entityConfiguration.cacheKeyVersion}; this would silently disable invalidation`,
);
}
return cacheKeyVersions.map((cacheKeyVersion) =>
this.makeCacheKeyForCacheKeyVersion(key, value, cacheKeyVersion),
);
}
}
}
}
@@ -1,125 +0,0 @@
import { EntityCacheAdapter, EntityConfiguration, CacheLoadResult, mapKeys } from '@expo/entity';
import invariant from 'invariant';
import type { Redis } from 'ioredis';
import GenericRedisCacher from './GenericRedisCacher';
export interface RedisCacheAdapterContext {
/**
* Instance of ioredis.Redis
*/
redisClient: Redis;
/**
* Create a key string for key parts (cache key prefix, versions, entity name, etc).
* Most commonly a simple `parts.join(':')`. See integration test for example.
*/
makeKeyFn: (...parts: string[]) => string;
/**
* Global cache version for the entity framework. Bumping this version will
* invalidate the cache for all entities at once.
*/
cacheKeyVersion: number;
/**
* Prefix prepended to all entity cache keys. Useful for adding a short, human-readable
* distintion for entity keys, e.g. `ent-`
*/
cacheKeyPrefix: string;
/**
* TTL for caching database hits. Successive entity loads within this TTL
* will be read from cache (unless invalidated).
*/
ttlSecondsPositive: number;
/**
* TTL for negatively caching database misses. Successive entity loads within
* this TTL will be assumed not present in the database (unless invalidated).
*/
ttlSecondsNegative: number;
}
export default class RedisCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
private readonly genericRedisCacher: GenericRedisCacher<TFields>;
constructor(
entityConfiguration: EntityConfiguration<TFields>,
private readonly context: RedisCacheAdapterContext
) {
super(entityConfiguration);
this.genericRedisCacher = new GenericRedisCacher(
{
redisClient: context.redisClient,
ttlSecondsNegative: context.ttlSecondsNegative,
ttlSecondsPositive: context.ttlSecondsPositive,
},
entityConfiguration
);
}
public async loadManyAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
const redisCacheKeyToFieldValueMapping = new Map(
fieldValues.map((fieldValue) => [this.makeCacheKey(fieldName, fieldValue), fieldValue])
);
const cacheResults = await this.genericRedisCacher.loadManyAsync(
Array.from(redisCacheKeyToFieldValueMapping.keys())
);
return mapKeys(cacheResults, (redisCacheKey) => {
const fieldValue = redisCacheKeyToFieldValueMapping.get(redisCacheKey);
invariant(
fieldValue !== undefined,
'Unspecified cache key %s returned from generic Redis cacher',
redisCacheKey
);
return fieldValue;
});
}
public async cacheManyAsync<N extends keyof TFields>(
fieldName: N,
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
): Promise<void> {
await this.genericRedisCacher.cacheManyAsync(
mapKeys(objectMap, (fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
public async cacheDBMissesAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<void> {
await this.genericRedisCacher.cacheDBMissesAsync(
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
public async invalidateManyAsync<N extends keyof TFields>(
fieldName: N,
fieldValues: readonly NonNullable<TFields[N]>[]
): Promise<void> {
await this.genericRedisCacher.invalidateManyAsync(
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
);
}
private makeCacheKey<N extends keyof TFields>(
fieldName: N,
fieldValue: NonNullable<TFields[N]>
): string {
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
invariant(columnName, `database field mapping missing for ${String(fieldName)}`);
return this.context.makeKeyFn(
this.context.cacheKeyPrefix,
this.entityConfiguration.tableName,
`v2.${this.entityConfiguration.cacheKeyVersion}`,
columnName,
String(fieldValue)
);
}
}
@@ -1,13 +1,19 @@
import { IEntityCacheAdapterProvider, EntityConfiguration, EntityCacheAdapter } from '@expo/entity';
import type {
EntityConfiguration,
IEntityCacheAdapter,
IEntityCacheAdapterProvider,
} from '@expo/entity';
import { GenericEntityCacheAdapter } from '@expo/entity';
import RedisCacheAdapter, { RedisCacheAdapterContext } from './RedisCacheAdapter';
import type { GenericRedisCacheContext } from './GenericRedisCacher.ts';
import { GenericRedisCacher } from './GenericRedisCacher.ts';
export default class RedisCacheAdapterProvider implements IEntityCacheAdapterProvider {
constructor(private readonly context: RedisCacheAdapterContext) {}
export class RedisCacheAdapterProvider implements IEntityCacheAdapterProvider {
constructor(private readonly context: GenericRedisCacheContext) {}
getCacheAdapter<TFields>(
entityConfiguration: EntityConfiguration<TFields>
): EntityCacheAdapter<TFields> {
return new RedisCacheAdapter(entityConfiguration, this.context);
getCacheAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
entityConfiguration: EntityConfiguration<TFields, TIDField>,
): IEntityCacheAdapter<TFields, TIDField> {
return new GenericEntityCacheAdapter(new GenericRedisCacher(this.context, entityConfiguration));
}
}
@@ -1,4 +1,4 @@
import { DateField } from '@expo/entity';
import { BufferField, DateField } from '@expo/entity';
export const redisTransformerMap = new Map([
[
@@ -22,4 +22,25 @@ export const redisTransformerMap = new Map([
read: (val: any) => (val ? new Date(val) : val),
},
],
[
BufferField.name,
{
/**
* All Redis fields are serialized to JSON before being written.
* In the case of buffers, they need to be explicitly reconstructed
* as JavaScript Buffer objects upon retrieval. We also explicitly
* serialize them to base64 strings to ensure no underlying Redis
* changes affect the fields.
*
* Write behavior: Value to write will always be either a Buffer or null.
* Behavior is to pass through null and convert buffers
* to base64 strings.
* Read behavior: Value will always be either null or a base64 string.
* behavior is to convert base64 strings back into Buffer
* objects and pass through null.
*/
write: (val: Buffer) => (val ? val.toString('base64') : null),
read: (val: any) => (val ? Buffer.from(val, 'base64') : val),
},
],
]);
@@ -0,0 +1,180 @@
import { Batcher } from '@expo/batcher';
import type { GenericEntityCacheAdapter } from '@expo/entity';
import { SingleFieldHolder, SingleFieldValueHolder, ViewerContext, zipToMap } from '@expo/entity';
import nullthrows from '@expo/nullthrows';
import { afterAll, beforeAll, beforeEach, describe, expect, it, jest } from '@jest/globals';
import invariant from 'invariant';
import { Redis } from 'ioredis';
import { URL } from 'url';
import { v4 as uuidv4 } from 'uuid';
import type { GenericRedisCacheContext, IRedis, IRedisTransaction } from '../GenericRedisCacher.ts';
import { GenericRedisCacher, RedisCacheInvalidationStrategy } from '../GenericRedisCacher.ts';
import type { RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity.ts';
import { RedisTestEntity } from '../__testfixtures__/RedisTestEntity.ts';
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider.ts';
class BatchedRedis implements IRedis {
private readonly mgetBatcher = new Batcher<string[], (string | null)[]>(
this.batchMgetAsync.bind(this),
{
maxBatchInterval: 0,
},
);
constructor(private readonly redis: Redis) {}
private async batchMgetAsync(keySets: string[][]): Promise<(string | null)[][]> {
// ordered distinct keys to fetch
const allKeysToFetch = [...new Set(keySets.flat())];
// fetch the distinct keys
const allResults = await this.redis.mget(...allKeysToFetch);
// put them into a map for fast lookup
const keysToResults = zipToMap(allKeysToFetch, allResults);
// re-associate them with original key sets
return keySets.map((keySet) =>
keySet.map((key) => {
const result = keysToResults.get(key);
invariant(result !== undefined, 'result should not be undefined');
return result;
}),
);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
async mget(...args: string[]): Promise<(string | null)[]> {
return await this.mgetBatcher.batchAsync(args);
}
multi(): IRedisTransaction {
return this.redis.multi();
}
// eslint-disable-next-line @typescript-eslint/naming-convention
async del(...args: string[]): Promise<void> {
await this.redis.del(...args);
}
}
describe(GenericRedisCacher, () => {
const redis = new Redis(new URL(process.env['REDIS_URL']!).toString());
const redisClient = new BatchedRedis(redis);
let genericRedisCacheContext: GenericRedisCacheContext;
beforeAll(() => {
genericRedisCacheContext = {
redisClient,
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'test-',
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
};
});
beforeEach(async () => {
await redis.flushdb();
});
afterAll(async () => {
redis.disconnect();
});
it('has correct caching behavior', async () => {
// simulate two requests
const viewerContext = new ViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const mgetSpy = jest.spyOn(redis, 'mget');
const genericCacher = (
viewerContext.entityCompanionProvider.getCompanionForEntity(RedisTestEntity)[
'tableDataCoordinator'
]['cacheAdapter'] as GenericEntityCacheAdapter<RedisTestEntityFields, 'id'>
)['genericCacher'];
const entity1Created = await RedisTestEntity.creator(viewerContext)
.setField('name', 'blah')
.createAsync();
// loading an entity should put it in cache. load by multiple requests and multiple fields in same tick to ensure batch works
mgetSpy.mockClear();
const viewerContext1 = new ViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const viewerContext2 = new ViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const viewerContext3 = new ViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const [entity1, entity2, entity3] = await Promise.all([
RedisTestEntity.loader(viewerContext1).loadByIDAsync(entity1Created.getID()),
RedisTestEntity.loader(viewerContext2).loadByIDAsync(entity1Created.getID()),
RedisTestEntity.loader(viewerContext3).loadByFieldEqualingAsync(
'name',
entity1Created.getField('name'),
),
]);
expect(mgetSpy).toHaveBeenCalledTimes(1);
expect(mgetSpy.mock.calls[0]).toHaveLength(2); // should dedupe the first two loads
expect(entity1.getID()).toEqual(entity2.getID());
expect(entity2.getID()).toEqual(nullthrows(entity3).getID());
const cacheKeyEntity1 = genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1Created.getID()),
);
const cachedJSON = await redis.get(cacheKeyEntity1);
const cachedValue = JSON.parse(cachedJSON!);
expect(cachedValue).toMatchObject({
id: entity1.getID(),
name: 'blah',
});
const cacheKeyEntity1NameField = genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('name'),
new SingleFieldValueHolder(entity1Created.getField('name')),
);
await RedisTestEntity.loader(viewerContext).loadByFieldEqualingAsync(
'name',
entity1Created.getField('name'),
);
await expect(redis.get(cacheKeyEntity1NameField)).resolves.toEqual(cachedJSON);
// simulate non existent db fetch, should write negative result ('') to cache
const nonExistentId = uuidv4();
const entityNonExistentResult =
await RedisTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult.ok).toBe(false);
const cacheKeyNonExistent = genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(nonExistentId),
);
const nonExistentCachedValue = await redis.get(cacheKeyNonExistent);
expect(nonExistentCachedValue).toEqual('');
// load again through entities framework to ensure it reads negative result
const entityNonExistentResult2 =
await RedisTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult2.ok).toBe(false);
// invalidate from cache to ensure it invalidates correctly in both caches
await RedisTestEntity.invalidationUtils(viewerContext).invalidateFieldsAsync(
entity1.getAllFields(),
);
await expect(redis.get(cacheKeyEntity1)).resolves.toBeNull();
await expect(redis.get(cacheKeyEntity1NameField)).resolves.toBeNull();
});
});
@@ -0,0 +1,228 @@
import type { GenericEntityCacheAdapter } from '@expo/entity';
import {
CompositeFieldHolder,
CompositeFieldValueHolder,
SingleFieldHolder,
SingleFieldValueHolder,
ViewerContext,
} from '@expo/entity';
import { enforceAsyncResult } from '@expo/results';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
import { Redis } from 'ioredis';
import { URL } from 'url';
import { v4 as uuidv4 } from 'uuid';
import type { GenericRedisCacheContext } from '../GenericRedisCacher.ts';
import { GenericRedisCacher, RedisCacheInvalidationStrategy } from '../GenericRedisCacher.ts';
import type { RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity.ts';
import { RedisTestEntity } from '../__testfixtures__/RedisTestEntity.ts';
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider.ts';
class TestViewerContext extends ViewerContext {}
describe(GenericRedisCacher, () => {
let genericRedisCacheContext: GenericRedisCacheContext;
beforeAll(() => {
genericRedisCacheContext = {
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'test-',
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
};
});
beforeEach(async () => {
await (genericRedisCacheContext.redisClient as Redis).flushdb();
});
afterAll(async () => {
(genericRedisCacheContext.redisClient as Redis).disconnect();
});
it('has correct caching behavior', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const genericCacher = (
viewerContext.entityCompanionProvider.getCompanionForEntity(RedisTestEntity)[
'tableDataCoordinator'
]['cacheAdapter'] as GenericEntityCacheAdapter<RedisTestEntityFields, 'id'>
)['genericCacher'];
const entity1Created = await RedisTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', new Date())
.setField('bufferField', Buffer.from('hello world'))
.createAsync();
// loading an entity should put it in cache
const entity1 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(
entity1Created.getID(),
);
const cachedSingleJSON = await (genericRedisCacheContext.redisClient as Redis).get(
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1.getID()),
),
);
const cachedSingleValue = JSON.parse(cachedSingleJSON!);
expect(cachedSingleValue).toMatchObject({
id: entity1.getID(),
name: 'blah',
});
// loading an entity by composite field should put it in cache
const entity2 = await RedisTestEntity.loader(viewerContext).loadByCompositeFieldEqualingAsync(
['name', 'id'],
{ name: 'blah', id: entity1.getID() },
);
const cachedCompositeJSON = await (genericRedisCacheContext.redisClient as Redis).get(
genericCacher.makeCacheKeyForStorage(
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
new CompositeFieldValueHolder({ id: entity2!.getID(), name: 'blah' }),
),
);
const cachedCompositeValue = JSON.parse(cachedCompositeJSON!);
expect(cachedCompositeValue).toMatchObject({
id: entity2!.getID(),
name: 'blah',
});
// simulate non existent db fetch, should write negative result ('') to cache
const nonExistentId = uuidv4();
const entityNonExistentResult =
await RedisTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedValue = await (genericRedisCacheContext.redisClient as Redis).get(
genericCacher.makeCacheKeyForStorage(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(nonExistentId),
),
);
expect(nonExistentCachedValue).toEqual('');
const entityNonExistentCompositeResult = await RedisTestEntity.loader(
viewerContext,
).loadByCompositeFieldEqualingAsync(['name', 'id'], { name: 'blah', id: nonExistentId });
expect(entityNonExistentCompositeResult).toBe(null);
const nonExistentCompositeCachedValue = await (
genericRedisCacheContext.redisClient as Redis
).get(
genericCacher.makeCacheKeyForStorage(
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
new CompositeFieldValueHolder({ id: nonExistentId, name: 'blah' }),
),
);
expect(nonExistentCompositeCachedValue).toEqual('');
// load again through entities framework to ensure it reads negative result
const entityNonExistentResult2 =
await RedisTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
nonExistentId,
);
expect(entityNonExistentResult2.ok).toBe(false);
const entityNonExistentCompositeResult2 = await RedisTestEntity.loader(
viewerContext,
).loadByCompositeFieldEqualingAsync(['name', 'id'], { name: 'blah', id: nonExistentId });
expect(entityNonExistentCompositeResult2).toBe(null);
// invalidate from cache to ensure it invalidates correctly
await RedisTestEntity.invalidationUtils(viewerContext).invalidateFieldsAsync(
entity1.getAllFields(),
);
const cachedValueNullKeys = genericCacher.makeCacheKeysForInvalidation(
new SingleFieldHolder('id'),
new SingleFieldValueHolder(entity1.getID()),
);
const cachedValueNull = await (genericRedisCacheContext.redisClient as Redis).mget(
...cachedValueNullKeys,
);
expect(cachedValueNull.every((c) => c === null)).toBe(true);
const cachedValueNullCompositeKeys = genericCacher.makeCacheKeysForInvalidation(
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
new CompositeFieldValueHolder({ id: entity1.getID(), name: 'blah' }),
);
const cachedValueNullComposite = await (genericRedisCacheContext.redisClient as Redis).mget(
...cachedValueNullCompositeKeys,
);
expect(cachedValueNullComposite.every((c) => c === null)).toBe(true);
});
it('caches and restores date fields', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const date = new Date();
const entity1 = await enforceAsyncResult(
RedisTestEntity.creatorWithAuthorizationResults(viewerContext)
.setField('dateField', date)
.createAsync(),
);
expect(entity1.getField('dateField')).toEqual(date);
const entity2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(entity1.getID());
expect(entity2.getField('dateField')).toEqual(date);
// simulate new request
const vc2 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const entity3 = await RedisTestEntity.loader(vc2).loadByIDAsync(entity1.getID());
expect(entity3.getField('dateField')).toEqual(date);
});
it('caches and restores empty string field keys', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const entity1 = await enforceAsyncResult(
RedisTestEntity.creatorWithAuthorizationResults(viewerContext)
.setField('name', '')
.createAsync(),
);
const entity2 = await RedisTestEntity.loader(viewerContext).loadByFieldEqualingAsync(
'name',
'',
);
expect(entity2?.getID()).toEqual(entity1.getID());
// simulate new request
const vc2 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const entity3 = await RedisTestEntity.loader(vc2).loadByFieldEqualingAsync('name', '');
expect(entity3?.getID()).toEqual(entity1.getID());
});
it('caches and restores buffer fields', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const buffer = Buffer.from('hello world');
const entity1 = await enforceAsyncResult(
RedisTestEntity.creatorWithAuthorizationResults(viewerContext)
.setField('bufferField', buffer)
.createAsync(),
);
expect(entity1.getField('bufferField')).toEqual(buffer);
const entity2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(entity1.getID());
expect(entity2.getField('bufferField')).toEqual(buffer);
// simulate new request
const vc2 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const entity3 = await RedisTestEntity.loader(vc2).loadByIDAsync(entity1.getID());
expect(entity3.getField('bufferField')).toEqual(buffer);
});
});
@@ -1,68 +1,64 @@
import { CacheStatus, ViewerContext } from '@expo/entity';
import Redis from 'ioredis';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
import { Redis } from 'ioredis';
import { URL } from 'url';
import GenericRedisCacher from '../GenericRedisCacher';
import { RedisCacheAdapterContext } from '../RedisCacheAdapter';
import RedisTestEntity, {
import type { GenericRedisCacheContext } from '../GenericRedisCacher.ts';
import { GenericRedisCacher, RedisCacheInvalidationStrategy } from '../GenericRedisCacher.ts';
import type { RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity.ts';
import {
RedisTestEntity,
redisTestEntityConfiguration,
RedisTestEntityFields,
} from '../testfixtures/RedisTestEntity';
import { createRedisIntegrationTestEntityCompanionProvider } from '../testfixtures/createRedisIntegrationTestEntityCompanionProvider';
} from '../__testfixtures__/RedisTestEntity.ts';
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider.ts';
class TestViewerContext extends ViewerContext {}
describe(GenericRedisCacher, () => {
let redisCacheAdapterContext: RedisCacheAdapterContext;
let genericRedisCacheContext: GenericRedisCacheContext;
beforeAll(() => {
redisCacheAdapterContext = {
genericRedisCacheContext = {
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
makeKeyFn(...parts: string[]): string {
const delimiter = ':';
const escapedParts = parts.map((part) =>
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
);
return escapedParts.join(delimiter);
},
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'test-',
cacheKeyVersion: 1,
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
};
});
beforeEach(async () => {
await redisCacheAdapterContext.redisClient.flushdb();
await (genericRedisCacheContext.redisClient as Redis).flushdb();
});
afterAll(async () => {
redisCacheAdapterContext.redisClient.disconnect();
(genericRedisCacheContext.redisClient as Redis).disconnect();
});
it('has correct caching and loading behavior', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const genericRedisCacher = new GenericRedisCacher(
{
redisClient: redisCacheAdapterContext.redisClient,
ttlSecondsNegative: redisCacheAdapterContext.ttlSecondsNegative,
ttlSecondsPositive: redisCacheAdapterContext.ttlSecondsPositive,
},
redisTestEntityConfiguration
genericRedisCacheContext,
redisTestEntityConfiguration,
);
const date = new Date();
const buffer = Buffer.from('hello world');
const entity1Created = await RedisTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.enforceCreateAsync();
.setField('bufferField', buffer)
.createAsync();
const testKey = `test-id-key-${entity1Created.getID()}`;
const objectMap = new Map<string, Readonly<RedisTestEntityFields>>([
[testKey, entity1Created.getAllFields()],
]);
await genericRedisCacher.cacheManyAsync(objectMap);
const cachedJSON = await redisCacheAdapterContext.redisClient.get(testKey);
const cachedJSON = await (genericRedisCacheContext.redisClient as Redis).get(testKey);
const cachedValue = JSON.parse(cachedJSON!);
expect(cachedValue).toMatchObject({
id: entity1Created.getID(),
@@ -77,14 +73,11 @@ describe(GenericRedisCacher, () => {
});
expect(loadedObjectMap.size).toBe(1);
});
it('has correct negative caching behaviour', async () => {
const genericRedisCacher = new GenericRedisCacher(
{
redisClient: redisCacheAdapterContext.redisClient,
ttlSecondsNegative: redisCacheAdapterContext.ttlSecondsNegative,
ttlSecondsPositive: redisCacheAdapterContext.ttlSecondsPositive,
},
redisTestEntityConfiguration
genericRedisCacheContext,
redisTestEntityConfiguration,
);
const testKey = `test-id-key-non-existent-id`;
@@ -93,23 +86,22 @@ describe(GenericRedisCacher, () => {
const cacheLoadResult = loadedObjectMap.get(testKey)!;
expect(cacheLoadResult.status).toBe(CacheStatus.NEGATIVE);
});
it('has correct invalidation behaviour', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
const genericRedisCacher = new GenericRedisCacher(
{
redisClient: redisCacheAdapterContext.redisClient,
ttlSecondsNegative: redisCacheAdapterContext.ttlSecondsNegative,
ttlSecondsPositive: redisCacheAdapterContext.ttlSecondsPositive,
},
redisTestEntityConfiguration
genericRedisCacheContext,
redisTestEntityConfiguration,
);
const date = new Date();
const buffer = Buffer.from('hello world');
const entity1Created = await RedisTestEntity.creator(viewerContext)
.setField('name', 'blah')
.setField('dateField', date)
.enforceCreateAsync();
.setField('bufferField', buffer)
.createAsync();
const testKey = `test-id-key-${entity1Created.getID()}`;
const objectMap = new Map<string, Readonly<RedisTestEntityFields>>([
[testKey, entity1Created.getAllFields()],
@@ -1,139 +0,0 @@
import { ViewerContext } from '@expo/entity';
import { enforceAsyncResult } from '@expo/results';
import Redis from 'ioredis';
import { URL } from 'url';
import { v4 as uuidv4 } from 'uuid';
import RedisCacheAdapter, { RedisCacheAdapterContext } from '../RedisCacheAdapter';
import RedisTestEntity from '../testfixtures/RedisTestEntity';
import { createRedisIntegrationTestEntityCompanionProvider } from '../testfixtures/createRedisIntegrationTestEntityCompanionProvider';
class TestViewerContext extends ViewerContext {}
describe(RedisCacheAdapter, () => {
let redisCacheAdapterContext: RedisCacheAdapterContext;
beforeAll(() => {
redisCacheAdapterContext = {
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
makeKeyFn(...parts: string[]): string {
const delimiter = ':';
const escapedParts = parts.map((part) =>
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
);
return escapedParts.join(delimiter);
},
cacheKeyPrefix: 'test-',
cacheKeyVersion: 1,
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
};
});
beforeEach(async () => {
await redisCacheAdapterContext.redisClient.flushdb();
});
afterAll(async () => {
redisCacheAdapterContext.redisClient.disconnect();
});
it('has correct caching behavior', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
);
const cacheAdapter = viewerContext.entityCompanionProvider.getCompanionForEntity(
RedisTestEntity,
RedisTestEntity.getCompanionDefinition()
)['tableDataCoordinator']['cacheAdapter'];
const cacheKeyMaker = cacheAdapter['makeCacheKey'].bind(cacheAdapter);
const entity1Created = await RedisTestEntity.creator(viewerContext)
.setField('name', 'blah')
.enforceCreateAsync();
// loading an entity should put it in cache
const entity1 = await RedisTestEntity.loader(viewerContext)
.enforcing()
.loadByIDAsync(entity1Created.getID());
const cachedJSON = await redisCacheAdapterContext.redisClient.get(
cacheKeyMaker('id', entity1.getID())
);
const cachedValue = JSON.parse(cachedJSON!);
expect(cachedValue).toMatchObject({
id: entity1.getID(),
name: 'blah',
});
// simulate non existent db fetch, should write negative result ('') to cache
const nonExistentId = uuidv4();
const entityNonExistentResult = await RedisTestEntity.loader(viewerContext).loadByIDAsync(
nonExistentId
);
expect(entityNonExistentResult.ok).toBe(false);
const nonExistentCachedValue = await redisCacheAdapterContext.redisClient.get(
cacheKeyMaker('id', nonExistentId)
);
expect(nonExistentCachedValue).toEqual('');
// load again through entities framework to ensure it reads negative result
const entityNonExistentResult2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(
nonExistentId
);
expect(entityNonExistentResult2.ok).toBe(false);
// invalidate from cache to ensure it invalidates correctly
await RedisTestEntity.loader(viewerContext).invalidateFieldsAsync(entity1.getAllFields());
const cachedValueNull = await redisCacheAdapterContext.redisClient.get(
cacheKeyMaker('id', entity1.getID())
);
expect(cachedValueNull).toBe(null);
});
it('caches and restores date fields', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
);
const date = new Date();
const entity1 = await enforceAsyncResult(
RedisTestEntity.creator(viewerContext).setField('dateField', date).createAsync()
);
expect(entity1.getField('dateField')).toEqual(date);
const entity2 = await RedisTestEntity.loader(viewerContext)
.enforcing()
.loadByIDAsync(entity1.getID());
expect(entity2.getField('dateField')).toEqual(date);
// simulate new request
const vc2 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
);
const entity3 = await RedisTestEntity.loader(vc2).enforcing().loadByIDAsync(entity1.getID());
expect(entity3.getField('dateField')).toEqual(date);
});
it('caches and restores empty string field keys', async () => {
const viewerContext = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
);
const entity1 = await enforceAsyncResult(
RedisTestEntity.creator(viewerContext).setField('name', '').createAsync()
);
const entity2 = await RedisTestEntity.loader(viewerContext)
.enforcing()
.loadByFieldEqualingAsync('name', '');
expect(entity2?.getID()).toEqual(entity1.getID());
// simulate new request
const vc2 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
);
const entity3 = await RedisTestEntity.loader(vc2)
.enforcing()
.loadByFieldEqualingAsync('name', '');
expect(entity3?.getID()).toEqual(entity1.getID());
});
});
@@ -1,46 +1,45 @@
import { EntityCacheAdapterTransientError, ViewerContext } from '@expo/entity';
import Redis from 'ioredis';
import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
import { Redis } from 'ioredis';
import { URL } from 'url';
import RedisCacheAdapter, { RedisCacheAdapterContext } from '../RedisCacheAdapter';
import RedisTestEntity from '../testfixtures/RedisTestEntity';
import { createRedisIntegrationTestEntityCompanionProvider } from '../testfixtures/createRedisIntegrationTestEntityCompanionProvider';
import type { GenericRedisCacheContext } from '../GenericRedisCacher.ts';
import { GenericRedisCacher, RedisCacheInvalidationStrategy } from '../GenericRedisCacher.ts';
import { RedisTestEntity } from '../__testfixtures__/RedisTestEntity.ts';
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider.ts';
class TestViewerContext extends ViewerContext {}
describe(RedisCacheAdapter, () => {
let redisCacheAdapterContext: RedisCacheAdapterContext;
describe(GenericRedisCacher, () => {
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
let genericRedisCacheContext: GenericRedisCacheContext;
beforeAll(() => {
redisCacheAdapterContext = {
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
makeKeyFn(...parts: string[]): string {
const delimiter = ':';
const escapedParts = parts.map((part) =>
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
);
return escapedParts.join(delimiter);
},
genericRedisCacheContext = {
redisClient,
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'test-',
cacheKeyVersion: 1,
ttlSecondsPositive: 86400, // 1 day
ttlSecondsNegative: 600, // 10 minutes
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
};
});
beforeEach(async () => {
await redisCacheAdapterContext.redisClient.flushdb();
await redisClient.flushdb();
});
it('throws when redis is disconnected', async () => {
redisCacheAdapterContext.redisClient.disconnect();
redisClient.disconnect();
const vc1 = new TestViewerContext(
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
);
await expect(
RedisTestEntity.creator(vc1).setField('name', 'blah').enforceCreateAsync()
RedisTestEntity.creator(vc1).setField('name', 'blah').createAsync(),
).rejects.toThrow(EntityCacheAdapterTransientError);
});
});
@@ -0,0 +1,83 @@
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
BufferField,
DateField,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
StringField,
UUIDField,
} from '@expo/entity';
export type RedisTestEntityFields = {
id: string;
name: string;
dateField: Date | null;
bufferField: Buffer | null;
};
export class RedisTestEntity extends Entity<RedisTestEntityFields, 'id', ViewerContext> {
static defineCompanionDefinition(): EntityCompanionDefinition<
RedisTestEntityFields,
'id',
ViewerContext,
RedisTestEntity,
RedisTestEntityPrivacyPolicy
> {
return {
entityClass: RedisTestEntity,
entityConfiguration: redisTestEntityConfiguration,
privacyPolicyClass: RedisTestEntityPrivacyPolicy,
};
}
}
export class RedisTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
RedisTestEntityFields,
'id',
ViewerContext,
RedisTestEntity
> {
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<RedisTestEntityFields, 'id', ViewerContext, RedisTestEntity>(),
];
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<RedisTestEntityFields, 'id', ViewerContext, RedisTestEntity>(),
];
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<RedisTestEntityFields, 'id', ViewerContext, RedisTestEntity>(),
];
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<RedisTestEntityFields, 'id', ViewerContext, RedisTestEntity>(),
];
}
export const redisTestEntityConfiguration = new EntityConfiguration<RedisTestEntityFields, 'id'>({
idField: 'id',
tableName: 'redis_test_entities',
schema: {
id: new UUIDField({
columnName: 'id',
cache: true,
}),
name: new StringField({
columnName: 'name',
cache: true,
}),
dateField: new DateField({
columnName: 'date_field',
}),
bufferField: new BufferField({
columnName: 'buffer_field',
cache: true,
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
compositeFieldDefinitions: [
{ compositeField: ['id', 'name'], cache: true },
{ compositeField: ['id', 'dateField'], cache: true },
{ compositeField: ['id', 'bufferField'], cache: true },
],
});
@@ -0,0 +1,36 @@
import type { IEntityMetricsAdapter } from '@expo/entity';
import { EntityCompanionProvider, NoOpEntityMetricsAdapter } from '@expo/entity';
import { StubDatabaseAdapterProvider, StubQueryContextProvider } from '@expo/entity-testing-utils';
import type { GenericRedisCacheContext } from '../GenericRedisCacher.ts';
import { RedisCacheAdapterProvider } from '../RedisCacheAdapterProvider.ts';
// share across all in calls in test to simulate postgres
const adapterProvider = new StubDatabaseAdapterProvider();
const queryContextProvider = new StubQueryContextProvider();
export const createRedisIntegrationTestEntityCompanionProvider = (
genericRedisCacheContext: GenericRedisCacheContext,
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter(),
): EntityCompanionProvider => {
return new EntityCompanionProvider(
metricsAdapter,
new Map([
[
'postgres',
{
adapterProvider,
queryContextProvider,
},
],
]),
new Map([
[
'redis',
{
cacheAdapterProvider: new RedisCacheAdapterProvider(genericRedisCacheContext),
},
],
]),
);
};
@@ -0,0 +1,428 @@
import {
CacheStatus,
CompositeFieldHolder,
CompositeFieldValueHolder,
EntityConfiguration,
SingleFieldHolder,
SingleFieldValueHolder,
StringField,
UUIDField,
} from '@expo/entity';
import { describe, expect, it } from '@jest/globals';
import type { Pipeline, Redis } from 'ioredis';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import { GenericRedisCacher, RedisCacheInvalidationStrategy } from '../GenericRedisCacher.ts';
type BlahFields = {
id: string;
fieldA: string;
fieldB: string;
};
const entityConfiguration = new EntityConfiguration<BlahFields, 'id'>({
idField: 'id',
tableName: 'blah',
cacheKeyVersion: 2,
schema: {
id: new UUIDField({ columnName: 'id', cache: true }),
fieldA: new StringField({ columnName: 'field_a', cache: true }),
fieldB: new StringField({ columnName: 'field_b', cache: true }),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
});
describe(GenericRedisCacher, () => {
describe('loadManyAsync', () => {
it('returns appropriate cache results', async () => {
const redisResults = new Map();
const mockRedisClient = mock<Redis>();
// need to have one anything() for each element of spread, in this case 3
when(mockRedisClient.mget(anything(), anything(), anything())).thenCall(async (...keys) =>
keys.map((k) => redisResults.get(k) ?? null),
);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const cacheKeyWat = genericCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
const cacheKeyWho = genericCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('who'),
);
const cacheKeyWhy = genericCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('why'),
);
redisResults.set(cacheKeyWat, JSON.stringify({ id: 'wat' }));
redisResults.set(cacheKeyWho, '');
const results = await genericCacher.loadManyAsync([cacheKeyWat, cacheKeyWho, cacheKeyWhy]);
expect(results.get(cacheKeyWat)).toMatchObject({
status: CacheStatus.HIT,
item: { id: 'wat' },
});
expect(results.get(cacheKeyWho)).toMatchObject({ status: CacheStatus.NEGATIVE });
expect(results.get(cacheKeyWhy)).toMatchObject({ status: CacheStatus.MISS });
expect(results.size).toBe(3);
});
it('returns empty map when passed empty array of fieldValues', async () => {
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mock<Redis>()),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const results = await genericCacher.loadManyAsync([]);
expect(results).toEqual(new Map());
});
});
describe('cacheManyAsync', () => {
it('correctly caches all objects', async () => {
const redisResults = new Map();
const mockPipeline = mock<Pipeline>();
when(mockPipeline.set(anything(), anything(), anything(), anything())).thenCall(
(key, value, code, ttl) => {
redisResults.set(key, { value, code, ttl });
return pipeline;
},
);
when(mockPipeline.exec()).thenResolve([]);
const pipeline = instance(mockPipeline);
const mockRedisClient = mock<Redis>();
when(mockRedisClient.multi()).thenReturn(pipeline);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const cacheKey = genericCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
const value = { id: 'wat', fieldA: 'a', fieldB: 'b' };
await genericCacher.cacheManyAsync(new Map([[cacheKey, value]]));
expect(redisResults.get(cacheKey)).toMatchObject({
value: JSON.stringify(value),
code: 'EX',
ttl: 1,
});
});
});
describe('cacheDBMissesAsync', () => {
it('correctly caches misses', async () => {
const redisResults = new Map();
const mockPipeline = mock<Pipeline>();
when(mockPipeline.set(anything(), anything(), anything(), anything())).thenCall(
(key, value, code, ttl) => {
redisResults.set(key, { value, code, ttl });
return pipeline;
},
);
when(mockPipeline.exec()).thenResolve([]);
const pipeline = instance(mockPipeline);
const mockRedisClient = mock<Redis>();
when(mockRedisClient.multi()).thenReturn(pipeline);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const cacheKey = genericCacher['makeCacheKeyForStorage'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
await genericCacher.cacheDBMissesAsync([cacheKey]);
expect(redisResults.get(cacheKey)).toMatchObject({
value: '',
code: 'EX',
ttl: 2,
});
});
});
describe('invalidateManyAsync', () => {
it('invalidates correctly with RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION', async () => {
const mockRedisClient = mock<Redis>();
when(mockRedisClient.del()).thenResolve(1);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const cacheKeys = genericCacher['makeCacheKeysForInvalidation'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
expect(cacheKeys).toHaveLength(1);
expect(cacheKeys[0]).toBe('hello-:single:blah:v4.2:id:wat');
await genericCacher.invalidateManyAsync(cacheKeys);
verify(mockRedisClient.del(...cacheKeys)).once();
});
it('does not collapse composite cache keys with delimiter-bearing parts', () => {
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mock<Redis>()),
cacheKeyPrefix: 'hello-',
cacheKeyDelimiter: ':',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
const compositeFieldHolder = new CompositeFieldHolder<BlahFields, 'id'>(['fieldA', 'fieldB']);
const cacheKey1 = genericCacher['makeCacheKeyForStorage'](
compositeFieldHolder,
new CompositeFieldValueHolder({ fieldA: 'a:b', fieldB: 'c' }),
);
const cacheKey2 = genericCacher['makeCacheKeyForStorage'](
compositeFieldHolder,
new CompositeFieldValueHolder({ fieldA: 'a', fieldB: 'b:c' }),
);
expect(cacheKey1).not.toEqual(cacheKey2);
});
it('invalidates correctly with RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS', async () => {
const mockRedisClient = mock<Redis>();
when(mockRedisClient.del()).thenResolve(1);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS,
},
},
entityConfiguration,
);
const cacheKeys = genericCacher['makeCacheKeysForInvalidation'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
expect(cacheKeys).toHaveLength(3);
expect(cacheKeys[0]).toBe('hello-:single:blah:v4.1:id:wat');
expect(cacheKeys[1]).toBe('hello-:single:blah:v4.2:id:wat');
expect(cacheKeys[2]).toBe('hello-:single:blah:v4.3:id:wat');
await genericCacher.invalidateManyAsync(cacheKeys);
verify(mockRedisClient.del(...cacheKeys)).once();
});
it('invalidates correctly with RedisCacheInvalidationStrategy.CUSTOM', async () => {
const mockRedisClient = mock<Redis>();
when(mockRedisClient.del()).thenResolve(1);
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mockRedisClient),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CUSTOM,
cacheKeyVersionsToInvalidateFn(cacheKeyVersion) {
return [
cacheKeyVersion,
cacheKeyVersion + 1,
cacheKeyVersion + 2,
cacheKeyVersion + 3,
];
},
},
},
entityConfiguration,
);
const cacheKeys = genericCacher['makeCacheKeysForInvalidation'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
);
expect(cacheKeys).toHaveLength(4);
expect(cacheKeys[0]).toBe('hello-:single:blah:v4.2:id:wat');
expect(cacheKeys[1]).toBe('hello-:single:blah:v4.3:id:wat');
expect(cacheKeys[2]).toBe('hello-:single:blah:v4.4:id:wat');
expect(cacheKeys[3]).toBe('hello-:single:blah:v4.5:id:wat');
await genericCacher.invalidateManyAsync(cacheKeys);
verify(mockRedisClient.del(...cacheKeys)).once();
});
it('returns when passed empty array of fieldValues', async () => {
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mock<Redis>()),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
},
},
entityConfiguration,
);
await genericCacher.invalidateManyAsync([]);
});
it('throws when CUSTOM strategy returns an empty cacheKeyVersions list', () => {
const genericCacher = new GenericRedisCacher(
{
redisClient: instance(mock<Redis>()),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CUSTOM,
cacheKeyVersionsToInvalidateFn: () => [],
},
},
entityConfiguration,
);
expect(() =>
genericCacher['makeCacheKeysForInvalidation'](
new SingleFieldHolder('id'),
new SingleFieldValueHolder('wat'),
),
).toThrow(/empty list/);
});
});
describe('GenericRedisCacheContext validation', () => {
const validContext = {
redisClient: instance(mock<Redis>()),
cacheKeyDelimiter: ':',
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
invalidationConfig: {
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION as const,
},
};
it('throws when cacheKeyDelimiter is empty', () => {
expect(
() =>
new GenericRedisCacher({ ...validContext, cacheKeyDelimiter: '' }, entityConfiguration),
).toThrow(/cacheKeyDelimiter must be a non-empty string/);
});
it('throws when cacheKeyDelimiter contains the escape character', () => {
expect(
() =>
new GenericRedisCacher({ ...validContext, cacheKeyDelimiter: '\\' }, entityConfiguration),
).toThrow(/must not contain the escape character/);
});
it('throws when ttlSecondsPositive is not a positive integer', () => {
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsPositive: 0 }, entityConfiguration),
).toThrow(/ttlSecondsPositive must be a positive integer/);
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsPositive: -1 }, entityConfiguration),
).toThrow(/ttlSecondsPositive must be a positive integer/);
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsPositive: 1.5 }, entityConfiguration),
).toThrow(/ttlSecondsPositive must be a positive integer/);
});
it('throws when ttlSecondsNegative is not a positive integer', () => {
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsNegative: 0 }, entityConfiguration),
).toThrow(/ttlSecondsNegative must be a positive integer/);
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsNegative: -1 }, entityConfiguration),
).toThrow(/ttlSecondsNegative must be a positive integer/);
expect(
() =>
new GenericRedisCacher({ ...validContext, ttlSecondsNegative: 1.5 }, entityConfiguration),
).toThrow(/ttlSecondsNegative must be a positive integer/);
});
it('accepts a valid context', () => {
expect(() => new GenericRedisCacher(validContext, entityConfiguration)).not.toThrow();
});
});
});
@@ -1,169 +0,0 @@
import { CacheStatus, UUIDField, EntityConfiguration } from '@expo/entity';
import { Redis, Pipeline } from 'ioredis';
import { mock, when, instance, anything, verify } from 'ts-mockito';
import RedisCacheAdapter from '../RedisCacheAdapter';
type BlahFields = {
id: string;
};
const entityConfiguration = new EntityConfiguration<BlahFields>({
idField: 'id',
tableName: 'blah',
schema: {
id: new UUIDField({ columnName: 'id', cache: true }),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
});
describe(RedisCacheAdapter, () => {
describe('loadManyAsync', () => {
it('returns appropriate cache results', async () => {
const redisResults = new Map();
const mockRedisClient = mock<Redis>();
// need to have one anything() for each element of spread, in this case 3
when(mockRedisClient.mget(anything(), anything(), anything())).thenCall(async (...keys) =>
keys.map((k) => redisResults.get(k) ?? null)
);
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mockRedisClient),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
redisResults.set(cacheAdapter['makeCacheKey']('id', 'wat'), JSON.stringify({ id: 'wat' }));
redisResults.set(cacheAdapter['makeCacheKey']('id', 'who'), '');
const results = await cacheAdapter.loadManyAsync('id', ['wat', 'who', 'why']);
expect(results.get('wat')).toMatchObject({ status: CacheStatus.HIT, item: { id: 'wat' } });
expect(results.get('who')).toMatchObject({ status: CacheStatus.NEGATIVE });
expect(results.get('why')).toMatchObject({ status: CacheStatus.MISS });
expect(results.size).toBe(3);
});
it('returns empty map when passed empty array of fieldValues', async () => {
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mock<Redis>()),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
const results = await cacheAdapter.loadManyAsync('id', []);
expect(results).toEqual(new Map());
});
});
describe('cacheManyAsync', () => {
it('correctly caches all objects', async () => {
const redisResults = new Map();
const mockPipeline = mock<Pipeline>();
when(mockPipeline.set(anything(), anything(), anything(), anything())).thenCall(
(key, value, code, ttl) => {
redisResults.set(key, { value, code, ttl });
return pipeline;
}
);
when(mockPipeline.exec()).thenResolve({} as any);
const pipeline = instance(mockPipeline);
const mockRedisClient = mock<Redis>();
when(mockRedisClient.multi()).thenReturn(pipeline);
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mockRedisClient),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
await cacheAdapter.cacheManyAsync('id', new Map([['wat', { id: 'wat' }]]));
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
expect(redisResults.get(cacheKey)).toMatchObject({
value: JSON.stringify({ id: 'wat' }),
code: 'EX',
ttl: 1,
});
});
});
describe('cacheDBMissesAsync', () => {
it('correctly caches misses', async () => {
const redisResults = new Map();
const mockPipeline = mock<Pipeline>();
when(mockPipeline.set(anything(), anything(), anything(), anything())).thenCall(
(key, value, code, ttl) => {
redisResults.set(key, { value, code, ttl });
return pipeline;
}
);
when(mockPipeline.exec()).thenResolve({} as any);
const pipeline = instance(mockPipeline);
const mockRedisClient = mock<Redis>();
when(mockRedisClient.multi()).thenReturn(pipeline);
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mockRedisClient),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
await cacheAdapter.cacheDBMissesAsync('id', ['wat']);
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
expect(redisResults.get(cacheKey)).toMatchObject({
value: '',
code: 'EX',
ttl: 2,
});
});
});
describe('invalidateManyAsync', () => {
it('invalidates correctly', async () => {
const mockRedisClient = mock<Redis>();
when(mockRedisClient.del()).thenResolve(1);
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mockRedisClient),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
await cacheAdapter.invalidateManyAsync('id', ['wat']);
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
verify(mockRedisClient.del(cacheKey)).once();
});
it('returns when passed empty array of fieldValues', async () => {
const cacheAdapter = new RedisCacheAdapter(entityConfiguration, {
redisClient: instance(mock<Redis>()),
makeKeyFn: (...parts) => parts.join(':'),
cacheKeyVersion: 1,
cacheKeyPrefix: 'hello-',
ttlSecondsPositive: 1,
ttlSecondsNegative: 2,
});
await cacheAdapter.invalidateManyAsync('id', []);
});
});
});
@@ -1,11 +1,12 @@
import { EntityCacheAdapterTransientError } from '@expo/entity';
import { describe, expect, it } from '@jest/globals';
import wrapNativeRedisCallAsync from '../wrapNativeRedisCallAsync';
import { wrapNativeRedisCallAsync } from '../wrapNativeRedisCallAsync.ts';
describe(wrapNativeRedisCallAsync, () => {
it('rethrows literals', async () => {
const throwingFn = async (): Promise<void> => {
// eslint-disable-next-line no-throw-literal,@typescript-eslint/no-throw-literal
// eslint-disable-next-line no-throw-literal,@typescript-eslint/only-throw-error
throw 'hello';
};
@@ -1,6 +1,6 @@
import { EntityCacheAdapterTransientError } from '@expo/entity';
export default async function wrapNativeRedisCallAsync<T>(fn: () => Promise<T>): Promise<T> {
export async function wrapNativeRedisCallAsync<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (e) {
@@ -4,9 +4,8 @@
* @module @expo/entity-cache-adapter-redis
*/
export { default as RedisCacheAdapter } from './RedisCacheAdapter';
export * from './RedisCacheAdapter';
export { default as RedisCacheAdapterProvider } from './RedisCacheAdapterProvider';
export * from './RedisCommon';
export { default as GenericRedisCacher } from './GenericRedisCacher';
export * from './GenericRedisCacher';
export * from './GenericRedisCacher.ts';
export * from './RedisCacheAdapterProvider.ts';
export * from './RedisCommon.ts';
export * from './errors/wrapNativeRedisCallAsync.ts';
export * from './utils/getSurroundingCacheKeyVersionsForInvalidation.ts';
@@ -1,95 +0,0 @@
import {
AlwaysAllowPrivacyPolicyRule,
EntityPrivacyPolicy,
ViewerContext,
UUIDField,
DateField,
StringField,
EntityConfiguration,
EntityCompanionDefinition,
Entity,
} from '@expo/entity';
export type RedisTestEntityFields = {
id: string;
name: string;
dateField: Date | null;
};
export default class RedisTestEntity extends Entity<RedisTestEntityFields, string, ViewerContext> {
static getCompanionDefinition(): EntityCompanionDefinition<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity,
RedisTestEntityPrivacyPolicy
> {
return redisTestEntityCompanionDefinition;
}
}
export class RedisTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity
> {
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity
>(),
];
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity
>(),
];
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity
>(),
];
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<
RedisTestEntityFields,
string,
ViewerContext,
RedisTestEntity
>(),
];
}
export const redisTestEntityConfiguration = new EntityConfiguration<RedisTestEntityFields>({
idField: 'id',
tableName: 'redis_test_entities',
schema: {
id: new UUIDField({
columnName: 'id',
cache: true,
}),
name: new StringField({
columnName: 'name',
cache: true,
}),
dateField: new DateField({
columnName: 'date_field',
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
});
const redisTestEntityCompanionDefinition = new EntityCompanionDefinition({
entityClass: RedisTestEntity,
entityConfiguration: redisTestEntityConfiguration,
privacyPolicyClass: RedisTestEntityPrivacyPolicy,
});
@@ -1,36 +0,0 @@
import {
NoOpEntityMetricsAdapter,
IEntityMetricsAdapter,
EntityCompanionProvider,
StubQueryContextProvider,
StubDatabaseAdapterProvider,
} from '@expo/entity';
import { RedisCacheAdapterContext } from '../RedisCacheAdapter';
import RedisCacheAdapterProvider from '../RedisCacheAdapterProvider';
export const createRedisIntegrationTestEntityCompanionProvider = (
redisCacheAdapterContext: RedisCacheAdapterContext,
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter()
): EntityCompanionProvider => {
return new EntityCompanionProvider(
metricsAdapter,
new Map([
[
'postgres',
{
adapterProvider: new StubDatabaseAdapterProvider(),
queryContextProvider: StubQueryContextProvider,
},
],
]),
new Map([
[
'redis',
{
cacheAdapterProvider: new RedisCacheAdapterProvider(redisCacheAdapterContext),
},
],
])
);
};
@@ -0,0 +1,11 @@
import { describe, expect, it } from '@jest/globals';
import { getSurroundingCacheKeyVersionsForInvalidation } from '../getSurroundingCacheKeyVersionsForInvalidation.ts';
describe(getSurroundingCacheKeyVersionsForInvalidation, () => {
it('returns the correct cache key versions to invalidate', () => {
expect(getSurroundingCacheKeyVersionsForInvalidation(0)).toEqual([0, 1]);
expect(getSurroundingCacheKeyVersionsForInvalidation(1)).toEqual([0, 1, 2]);
expect(getSurroundingCacheKeyVersionsForInvalidation(2)).toEqual([1, 2, 3]);
});
});
@@ -0,0 +1,9 @@
export function getSurroundingCacheKeyVersionsForInvalidation(
cacheKeyVersion: number,
): readonly number[] {
return [
...(cacheKeyVersion === 0 ? [] : [cacheKeyVersion - 1]),
cacheKeyVersion,
cacheKeyVersion + 1,
];
}
@@ -1,7 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "build",
},
"include": ["src"]
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"],
"references": [{ "path": "../entity" }, { "path": "../entity-testing-utils" }]
}
+145
View File
@@ -0,0 +1,145 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
**Note:** Version bump only for package @expo/entity-codemod
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
**Note:** Version bump only for package @expo/entity-codemod
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
**Note:** Version bump only for package @expo/entity-codemod
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
**Note:** Version bump only for package @expo/entity-codemod
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
**Note:** Version bump only for package @expo/entity-codemod
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package @expo/entity-codemod
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package @expo/entity-codemod
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
**Note:** Version bump only for package @expo/entity-codemod
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
**Note:** Version bump only for package @expo/entity-codemod
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
**Note:** Version bump only for package @expo/entity-codemod
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
**Note:** Version bump only for package @expo/entity-codemod
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
**Note:** Version bump only for package @expo/entity-codemod
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
**Note:** Version bump only for package @expo/entity-codemod
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-codemod
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
### Bug Fixes
- refactor install method to instead be free functions ([#441](https://github.com/expo/entity/issues/441)) ([51d632d](https://github.com/expo/entity/commit/51d632da1619dc039d5c41ce6533f63254ec4337)), closes [#410](https://github.com/expo/entity/issues/410)
- revert change to codemod now that Entity.knexLoader is supported again via subclass ([#443](https://github.com/expo/entity/issues/443)) ([ed6b2e0](https://github.com/expo/entity/commit/ed6b2e05539c77ee2abd2c6da74e2a451967d06e)), closes [#441](https://github.com/expo/entity/issues/441)
### Features
- add codemod for upcoming v0.55.0-v0.56.0 ([#413](https://github.com/expo/entity/issues/413)) ([aba0ce5](https://github.com/expo/entity/commit/aba0ce5b5f38cb7b01956cde14a4714e2a03abb4)), closes [#407](https://github.com/expo/entity/issues/407) [-#410](https://github.com/-/issues/410)
# [0.55.0](https://github.com/expo/entity/compare/v0.54.0...v0.55.0) (2026-01-27)
**Note:** Version bump only for package @expo/entity-codemod
# [0.54.0](https://github.com/expo/entity/compare/v0.53.0...v0.54.0) (2026-01-21)
### Bug Fixes
- **deps:** pin dependencies ([#328](https://github.com/expo/entity/issues/328)) ([5679f27](https://github.com/expo/entity/commit/5679f27209f1515ca5e626d858c4aa054b414625))
# [0.53.0](https://github.com/expo/entity/compare/v0.52.0...v0.53.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-codemod
# [0.52.0](https://github.com/expo/entity/compare/v0.51.0...v0.52.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-codemod
# [0.51.0](https://github.com/expo/entity/compare/v0.50.0...v0.51.0) (2025-11-21)
**Note:** Version bump only for package @expo/entity-codemod
# [0.50.0](https://github.com/expo/entity/compare/v0.49.0...v0.50.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-codemod
# [0.49.0](https://github.com/expo/entity/compare/v0.48.0...v0.49.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-codemod
# [0.48.0](https://github.com/expo/entity/compare/v0.47.0...v0.48.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-codemod
# [0.47.0](https://github.com/expo/entity/compare/v0.46.0...v0.47.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-codemod
# [0.46.0](https://github.com/expo/entity/compare/v0.45.0...v0.46.0) (2025-06-26)
**Note:** Version bump only for package @expo/entity-codemod
# [0.45.0](https://github.com/expo/entity/compare/v0.44.0...v0.45.0) (2025-06-06)
**Note:** Version bump only for package @expo/entity-codemod
# [0.44.0](https://github.com/expo/entity/compare/v0.43.0...v0.44.0) (2025-05-29)
**Note:** Version bump only for package @expo/entity-codemod
# [0.43.0](https://github.com/expo/entity/compare/v0.42.0...v0.43.0) (2025-04-10)
**Note:** Version bump only for package @expo/entity-codemod
# [0.42.0](https://github.com/expo/entity/compare/v0.41.0...v0.42.0) (2025-04-10)
**Note:** Version bump only for package @expo/entity-codemod
# [0.41.0](https://github.com/expo/entity/compare/v0.40.0...v0.41.0) (2025-03-07)
### Features
- Create codemod for v0.40.0 -> v0.41.0 ([#260](https://github.com/expo/entity/issues/260)) ([4a2b61f](https://github.com/expo/entity/commit/4a2b61f2bd8edfc19109ef4c8ac780eaf0f7795c))
# [0.40.0](https://github.com/expo/entity/compare/v0.39.0...v0.40.0) (2025-02-27)
### Features
- Create codemod for v0.39.0 -> v0.40.0 ([#259](https://github.com/expo/entity/issues/259)) ([d63ddd6](https://github.com/expo/entity/commit/d63ddd65997a441a914a71b158a111339970aeab))
### Reverts
- Revert "v0.40.0" ([fa78ce8](https://github.com/expo/entity/commit/fa78ce84e2f2eb8199e36fd51779fc68905001c0))
+27
View File
@@ -0,0 +1,27 @@
# @expo/entity-codemod
A package containing jscodeshift codemods for @expo/entity upgrades.
Codemods are transformations that run on your codebase programmatically. This allows for a large amount of changes to be applied without having to manually go through every file.
## Usage
These should be used via the [jscodeshift CLI](https://github.com/facebook/jscodeshift?tab=readme-ov-file#usage-cli).
For example:
```
yarn add -D @expo/entity-codemod
```
Then, in package.json scripts:
```json
"jscodeshift": "jscodeshift --extensions=ts --parser=ts"
```
And finally:
```sh
yarn jscodeshift src -t node_modules/@expo/entity-codemod/build/v0.39.0-v0.40.0.js
```
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@expo/entity-codemod",
"version": "0.72.0",
"description": "jscodeshift codemods for @expo/entity upgrades",
"keywords": [
"entity"
],
"license": "MIT",
"author": "Expo",
"files": [
"build",
"!*.tsbuildinfo",
"!__*",
"src"
],
"type": "module",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"scripts": {
"build": "tsc --build",
"prepack": "rm -rf build && yarn build",
"clean": "yarn build --clean",
"lint": "yarn run --top-level oxlint --type-aware src",
"lint-fix": "yarn lint --fix",
"test": "yarn test:all --rootDir $(pwd)"
},
"devDependencies": {
"@jest/globals": "30.5.2",
"@types/jscodeshift": "17.3.0",
"@types/node": "24.19.2",
"jscodeshift": "17.4.0",
"typescript": "6.0.3"
},
"engines": {
"node": ">=18"
}
}
+2
View File
@@ -0,0 +1,2 @@
// oxlint-disable unicorn/no-empty-file
// placeholder file to fix tsc/typedoc
@@ -0,0 +1,79 @@
/* eslint-disable */
import { ViewerContext } from '@expo/entity';
async function testWithAuthorizationResults(viewerContext: ViewerContext): Promise<void> {
// creator
const entityResult = await TestEntity.creator(viewerContext).setField('wat', 2).createAsync();
// updater
const updatedEntityResult = await TestEntity.updater(entityResult.enforceValue()).setField('wat', 3).updateAsync();
// deleter
await TestEntity.deleteAsync(updatedEntityResult.enforceValue());
}
async function testEnforcing(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).setField('wat', 2).enforceCreateAsync();
const entity2 = await TestEntity.creator(viewerContext).setField('wat', 2).setField('who', 3).enforceCreateAsync();
const entity3 = await TestEntity.creator(viewerContext)
.setField('wat', 2)
.setField('who', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.enforceCreateAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).setField('wat', 3).enforceUpdateAsync();
const updatedEntity2 = await TestEntity.updater(entity2).setField('wat', 3).setField('who', 4).enforceUpdateAsync();
const updatedEntity3 = await TestEntity.updater(entity3)
.setField('wat', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.enforceUpdateAsync();
// deleter
await TestEntity.enforceDeleteAsync(entity);
}
async function testNoSetField(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).enforceCreateAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).enforceUpdateAsync();
}
async function testEnforcingWithQueryContext(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext, queryContext).setField('wat', 2).enforceCreateAsync();
// updater
const updatedEntity = await TestEntity.updater(entity, queryContext).setField('wat', 3).enforceUpdateAsync();
// deleter
await TestEntity.enforceDeleteAsync(entity, queryContext);
}
async function testAssociationLoader(): Promise<void> {
await this.associationLoader().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoader().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
}
@@ -0,0 +1,79 @@
/* eslint-disable */
import { ViewerContext } from '@expo/entity';
async function testWithAuthorizationResults(viewerContext: ViewerContext): Promise<void> {
// creator
const entityResult = await TestEntity.creator(viewerContext).withAuthorizationResults().setField('wat', 2).createAsync();
// updater
const updatedEntityResult = await TestEntity.updater(entityResult.enforceValue()).withAuthorizationResults().setField('wat', 3).updateAsync();
// deleter
await TestEntity.deleter(updatedEntityResult.enforceValue()).withAuthorizationResults().deleteAsync();
}
async function testEnforcing(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).enforcing().setField('wat', 2).createAsync();
const entity2 = await TestEntity.creator(viewerContext).enforcing().setField('wat', 2).setField('who', 3).createAsync();
const entity3 = await TestEntity.creator(viewerContext).enforcing()
.setField('wat', 2)
.setField('who', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).enforcing().setField('wat', 3).updateAsync();
const updatedEntity2 = await TestEntity.updater(entity2).enforcing().setField('wat', 3).setField('who', 4).updateAsync();
const updatedEntity3 = await TestEntity.updater(entity3).enforcing()
.setField('wat', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.updateAsync();
// deleter
await TestEntity.deleter(entity).enforcing().deleteAsync();
}
async function testNoSetField(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).enforcing().createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).enforcing().updateAsync();
}
async function testEnforcingWithQueryContext(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext, queryContext).enforcing().setField('wat', 2).createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity, queryContext).enforcing().setField('wat', 3).updateAsync();
// deleter
await TestEntity.deleter(entity, queryContext).enforcing().deleteAsync();
}
async function testAssociationLoader(): Promise<void> {
await this.associationLoader().withAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoader().withAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
}
@@ -0,0 +1,10 @@
/* eslint-disable */
async function testAsync(viewerContext: ViewerContext): Promise<void> {
const testTrigger = await TestTriggerEntity.createAsync(actorViewerContext, undefined, {
appId,
});
await TestTriggerEntity.enforceDeleteAsync(testTrigger);
return testTrigger;
};
@@ -0,0 +1,10 @@
/* eslint-disable */
async function testAsync(viewerContext: ViewerContext): Promise<void> {
const testTrigger = await TestTriggerEntity.createAsync(actorViewerContext, undefined, {
appId,
});
await TestTriggerEntity.deleter(testTrigger).enforcing().deleteAsync();
return testTrigger;
};
@@ -0,0 +1,5 @@
/* eslint-disable */
async function anotherCase(updater: any): Promise<any> {
return await updater.enforceUpdateAsync();
}
@@ -0,0 +1,5 @@
/* eslint-disable */
async function anotherCase(updater: any): Promise<any> {
return await updater.enforceUpdateAsync();
}
@@ -0,0 +1,100 @@
/* eslint-disable */
import { ViewerContext } from '@expo/entity';
async function testWithAuthorizationResults(viewerContext: ViewerContext): Promise<void> {
// creator
const entityResult = await TestEntity.creator(viewerContext).withAuthorizationResults().setField('wat', 2).createAsync();
// updater
const updatedEntityResult = await TestEntity.updater(entityResult.enforceValue()).withAuthorizationResults().setField('wat', 3).updateAsync();
// loader
const loadedEntityResult = await TestEntity.loader(viewerContext).withAuthorizationResults().loadByIDAsync('test');
// deleter
await TestEntity.deleter(updatedEntityResult.enforceValue()).withAuthorizationResults().deleteAsync();
}
async function testEnforcing(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).enforcing().setField('wat', 2).createAsync();
const entity2 = await TestEntity.creator(viewerContext).enforcing().setField('wat', 2).setField('who', 3).createAsync();
const entity3 = await TestEntity.creator(viewerContext).enforcing()
.setField('wat', 2)
.setField('who', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).enforcing().setField('wat', 3).updateAsync();
const updatedEntity2 = await TestEntity.updater(entity2).enforcing().setField('wat', 3).setField('who', 4).updateAsync();
const updatedEntity3 = await TestEntity.updater(entity3).enforcing()
.setField('wat', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.updateAsync();
// loader
const loadedEntity = await TestEntity.loader(viewerContext).enforcing().loadByIDAsync('test');
// deleter
await TestEntity.deleter(entity).enforcing().deleteAsync();
}
async function testNoSetField(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).enforcing().createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).enforcing().updateAsync();
}
async function testEnforcingWithQueryContext(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext, queryContext).enforcing().setField('wat', 2).createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity, queryContext).enforcing().setField('wat', 3).updateAsync();
// loader
const loadedEntity = await TestEntity.loader(viewerContext, queryContext).enforcing().loadByIDAsync('test');
// deleter
await TestEntity.deleter(entity, queryContext).enforcing().deleteAsync();
}
async function testAssociationLoader(): Promise<void> {
await this.associationLoader().withAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoader().withAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
await this.associationLoader().enforcing().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoader().enforcing().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
}
@@ -0,0 +1,100 @@
/* eslint-disable */
import { ViewerContext } from '@expo/entity';
async function testWithAuthorizationResults(viewerContext: ViewerContext): Promise<void> {
// creator
const entityResult = await TestEntity.creatorWithAuthorizationResults(viewerContext).setField('wat', 2).createAsync();
// updater
const updatedEntityResult = await TestEntity.updaterWithAuthorizationResults(entityResult.enforceValue()).setField('wat', 3).updateAsync();
// loader
const loadedEntityResult = await TestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync('test');
// deleter
await TestEntity.deleterWithAuthorizationResults(updatedEntityResult.enforceValue()).deleteAsync();
}
async function testEnforcing(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).setField('wat', 2).createAsync();
const entity2 = await TestEntity.creator(viewerContext).setField('wat', 2).setField('who', 3).createAsync();
const entity3 = await TestEntity.creator(viewerContext)
.setField('wat', 2)
.setField('who', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).setField('wat', 3).updateAsync();
const updatedEntity2 = await TestEntity.updater(entity2).setField('wat', 3).setField('who', 4).updateAsync();
const updatedEntity3 = await TestEntity.updater(entity3)
.setField('wat', 3)
.setField('who', 4)
.setField('who', 5)
.setField('who', 6)
.setField('who', 7)
.setField('who', 8)
.setField('who', 9)
.updateAsync();
// loader
const loadedEntity = await TestEntity.loader(viewerContext).loadByIDAsync('test');
// deleter
await TestEntity.deleter(entity).deleteAsync();
}
async function testNoSetField(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext).createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity).updateAsync();
}
async function testEnforcingWithQueryContext(viewerContext: ViewerContext): Promise<void> {
// creator
const entity = await TestEntity.creator(viewerContext, queryContext).setField('wat', 2).createAsync();
// updater
const updatedEntity = await TestEntity.updater(entity, queryContext).setField('wat', 3).updateAsync();
// loader
const loadedEntity = await TestEntity.loader(viewerContext, queryContext).loadByIDAsync('test');
// deleter
await TestEntity.deleter(entity, queryContext).deleteAsync();
}
async function testAssociationLoader(): Promise<void> {
await this.associationLoaderWithAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoaderWithAuthorizationResults().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
await this.associationLoader().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
);
await entity.associationLoader().loadAssociatedEntityAsync(
'another_id',
AnotherEntity,
queryContext
)
}
@@ -0,0 +1,37 @@
import { ViewerContext } from '@expo/entity';
import { PostEntity } from './entities/PostEntity.ts';
import { UserEntity } from './entities/UserEntity.ts';
async function loadUser(viewerContext: ViewerContext) {
// Basic loader calls - only transformed when using knex-specific methods
const userLoader = UserEntity.loader(viewerContext);
const postLoader = PostEntity.loader(viewerContext);
// These use knex-specific methods, so they should be transformed
const posts = await postLoader.loadManyByFieldEqualityConjunctionAsync([
{ fieldName: 'status', fieldValue: 'published' }
]);
const firstPost = await postLoader.loadFirstByFieldEqualityConjunctionAsync([
{ fieldName: 'id', fieldValue: '123' }
]);
// Loader with authorization results - only transformed when using knex methods
const userLoaderWithAuth = UserEntity.loaderWithAuthorizationResults(viewerContext);
const rawResults = await userLoaderWithAuth.loadManyByRawWhereClauseAsync('age > ?', [18]);
// Loader that doesn't use knex methods - should NOT be transformed
const standardLoader = PostEntity.loader(viewerContext);
const post = await standardLoader.loadByIDAsync('456');
// Should not transform instance methods or other properties
const user = await userLoader.loadByIDAsync('123');
const userLoadMethod = user.loader; // This should not be transformed
// Should not transform lowercase object methods
const customLoader = {
loader: (ctx: any) => ctx,
};
customLoader.loader(viewerContext);
return user;
}
@@ -0,0 +1,37 @@
import { ViewerContext } from '@expo/entity';
import { PostEntity } from './entities/PostEntity.ts';
import { UserEntity } from './entities/UserEntity.ts';
async function loadUser(viewerContext: ViewerContext) {
// Basic loader calls - only transformed when using knex-specific methods
const userLoader = UserEntity.loader(viewerContext);
const postLoader = PostEntity.knexLoader(viewerContext);
// These use knex-specific methods, so they should be transformed
const posts = await postLoader.loadManyByFieldEqualityConjunctionAsync([
{ fieldName: 'status', fieldValue: 'published' }
]);
const firstPost = await postLoader.loadFirstByFieldEqualityConjunctionAsync([
{ fieldName: 'id', fieldValue: '123' }
]);
// Loader with authorization results - only transformed when using knex methods
const userLoaderWithAuth = UserEntity.knexLoaderWithAuthorizationResults(viewerContext);
const rawResults = await userLoaderWithAuth.loadManyByRawWhereClauseAsync('age > ?', [18]);
// Loader that doesn't use knex methods - should NOT be transformed
const standardLoader = PostEntity.loader(viewerContext);
const post = await standardLoader.loadByIDAsync('456');
// Should not transform instance methods or other properties
const user = await userLoader.loadByIDAsync('123');
const userLoadMethod = user.loader; // This should not be transformed
// Should not transform lowercase object methods
const customLoader = {
loader: (ctx: any) => ctx,
};
customLoader.loader(viewerContext);
return user;
}
@@ -0,0 +1,33 @@
import { ViewerContext } from '@expo/entity';
import { CommentEntity } from './entities/CommentEntity.ts';
// Chained calls
const loadComments = async (viewerContext: ViewerContext) => {
// Direct chaining with knex-specific method
const comments = await CommentEntity.loader(viewerContext)
.loadManyByFieldEqualityConjunctionAsync([
{ fieldName: 'postId', fieldValue: '123' }
]);
// Direct chaining with regular method - should NOT be transformed
const singleComment = await CommentEntity
.loader(viewerContext)
.loadByIDAsync('456');
// With authorization results and knex method
const commentsWithAuth = await CommentEntity
.loaderWithAuthorizationResults(viewerContext)
.loadManyByRawWhereClauseAsync('postId = ?', ['456']);
// Edge cases - these should NOT be transformed
const anotherEntity = {
loader: (ctx: any) => ctx, // This is not an entity class
};
anotherEntity.loader(viewerContext); // Should NOT be transformed (lowercase object)
// Complex chaining with regular method - should NOT be transformed
return CommentEntity
.loader(viewerContext)
.withAuthenticationResults()
.loadByIDAsync('789');
};
@@ -0,0 +1,33 @@
import { ViewerContext } from '@expo/entity';
import { CommentEntity } from './entities/CommentEntity.ts';
// Chained calls
const loadComments = async (viewerContext: ViewerContext) => {
// Direct chaining with knex-specific method
const comments = await CommentEntity.knexLoader(viewerContext)
.loadManyByFieldEqualityConjunctionAsync([
{ fieldName: 'postId', fieldValue: '123' }
]);
// Direct chaining with regular method - should NOT be transformed
const singleComment = await CommentEntity
.loader(viewerContext)
.loadByIDAsync('456');
// With authorization results and knex method
const commentsWithAuth = await CommentEntity
.knexLoaderWithAuthorizationResults(viewerContext)
.loadManyByRawWhereClauseAsync('postId = ?', ['456']);
// Edge cases - these should NOT be transformed
const anotherEntity = {
loader: (ctx: any) => ctx, // This is not an entity class
};
anotherEntity.loader(viewerContext); // Should NOT be transformed (lowercase object)
// Complex chaining with regular method - should NOT be transformed
return CommentEntity
.loader(viewerContext)
.withAuthenticationResults()
.loadByIDAsync('789');
};
@@ -0,0 +1,28 @@
import { readFile, readdir } from 'fs/promises';
import { defineInlineTest } from 'jscodeshift/src/testUtils.js';
async function readfixtureAsync(mod: string, name: string): Promise<string> {
return await readFile(new URL(`../__testfixtures__/${mod}/${name}`, import.meta.url), {
encoding: 'utf8',
});
}
function prefixes(suffix: string): (names: string[]) => string[] {
return (names) => names.filter((n) => n.endsWith(suffix)).map((n) => n.replace(suffix, ''));
}
const modules = await readdir(new URL('../', import.meta.url)).then(prefixes('.ts'));
for (const mod of modules) {
const transform = await import(`../${mod}.ts`);
const fixtureDir = new URL(`../__testfixtures__/${mod}`, import.meta.url);
const fixtures = await readdir(fixtureDir).then(prefixes('.input.ts'));
for (const fixture of fixtures) {
defineInlineTest(
transform,
{},
await readfixtureAsync(mod, `${fixture}.input.ts`),
await readfixtureAsync(mod, `${fixture}.output.ts`),
);
}
}
@@ -0,0 +1,213 @@
import type { API, Collection, FileInfo, Options } from 'jscodeshift';
function transformEnforcingEntityChainMethod(
j: API['jscodeshift'],
root: Collection<any>,
type: 'enforcing' | 'withAuthorizationResults',
crudType: 'creator' | 'updater',
methodBefore: string,
methodAfter: string,
): void {
// Find all entity creator expressions of the form
// `TestEntity.creator(viewerContext).setField('wat', 2).enforceCreateAsync()`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: methodBefore,
},
},
})
.forEach((path) => {
const enforceCreateAsyncCallExpression = path.node; // TestEntity.creator(viewerContext).setField('wat', 2).enforceCreateAsync()
const enforceCreateAsyncCallee = enforceCreateAsyncCallExpression.callee; // TestEntity.creator(viewerContext).setField('wat', 2).enforceCreateAsync
if (enforceCreateAsyncCallee.type !== 'MemberExpression') {
return;
}
if (enforceCreateAsyncCallee.property.type !== 'Identifier') {
return;
}
// traverse in until we find the first non-setField call
let lastCallee = enforceCreateAsyncCallee;
while (true) {
// TestEntity.creator(viewerContext).setField('wat', 2)
const maybeSetFieldObjectCallExpression = lastCallee.object;
if (maybeSetFieldObjectCallExpression.type !== 'CallExpression') {
break;
}
// TestEntity.creator(viewerContext).setField
const maybeSetFieldObjectCallee = maybeSetFieldObjectCallExpression.callee;
if (maybeSetFieldObjectCallee.type !== 'MemberExpression') {
break;
}
const maybeSetFieldObjectCalleeProperty = maybeSetFieldObjectCallee.property;
if (maybeSetFieldObjectCalleeProperty.type !== 'Identifier') {
break;
}
if (maybeSetFieldObjectCalleeProperty.name === 'setField') {
lastCallee = maybeSetFieldObjectCallee;
} else {
break;
}
}
// TestEntity.creator(viewerContext)
const maybeCreatorCallExpression = lastCallee.object;
if (maybeCreatorCallExpression.type !== 'CallExpression') {
return;
}
if (
maybeCreatorCallExpression.callee.type !== 'MemberExpression' ||
maybeCreatorCallExpression.callee.property.type !== 'Identifier' ||
maybeCreatorCallExpression.callee.property.name !== crudType
) {
return;
}
// TestEntity.creator(viewerContext).enforcing()
const newCreatorCallExpression = j.callExpression(
j.memberExpression(maybeCreatorCallExpression, j.identifier(type)),
[],
);
// TestEntity.creator(viewerContext).enforcing().setField('wat', 2).createAsync()
lastCallee.object = newCreatorCallExpression;
// change enforceCreateAsync to createAsync at the end so if this returns early it doesn't mutate
enforceCreateAsyncCallee.property.name = methodAfter;
});
}
function transformEnforcingEntityDeleteMethod(
j: API['jscodeshift'],
root: Collection<any>,
type: 'enforcing' | 'withAuthorizationResults',
methodBefore: string,
methodAfter: string,
): void {
// Find all entity creator expressions of the form
// `TestEntity.enforceDeleteAsync(entity)`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: methodBefore,
},
},
})
.forEach((path) => {
const enforceDeleteAsyncCallExpression = path.node; // TestEntity.enforceDeleteAsync(entity)
const args = enforceDeleteAsyncCallExpression.arguments; // [entity]
enforceDeleteAsyncCallExpression.arguments = [];
const enforceDeleteAsyncCallee = enforceDeleteAsyncCallExpression.callee; // TestEntity.enforceDeleteAsync
if (enforceDeleteAsyncCallee.type !== 'MemberExpression') {
return;
}
// change enforceDeleteAsync to deleteAsync
if (enforceDeleteAsyncCallee.property.type !== 'Identifier') {
return;
}
enforceDeleteAsyncCallee.property.name = methodAfter;
enforceDeleteAsyncCallee.object = j.callExpression(
j.memberExpression(
j.callExpression(
j.memberExpression(enforceDeleteAsyncCallee.object, j.identifier('deleter')),
args,
),
j.identifier(type),
),
[],
);
});
}
function transformAssociationLoaderMethod(j: API['jscodeshift'], root: Collection<any>): void {
// Find all entity associationLoader expressions of the form
// `this.associationLoader()`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: 'associationLoader',
},
},
})
.forEach((path) => {
const associationLoaderCallExpression = path.node; // this.associationLoader()
// replace with this.associationLoader().withAuthorizationResults()
path.replace(
j.callExpression(
j.memberExpression(
associationLoaderCallExpression,
j.identifier('withAuthorizationResults'),
),
[],
),
);
});
}
export default function transformer(file: FileInfo, api: API, _options: Options): string {
const j = api.jscodeshift;
const root = j.withParser('ts')(file.source);
// do authorization results first since it uses the same detection pattern as enforcing post-transform (i.e. it looks for createAsync)
transformEnforcingEntityChainMethod(
j,
root,
'withAuthorizationResults',
'creator',
'createAsync',
'createAsync',
);
transformEnforcingEntityChainMethod(
j,
root,
'withAuthorizationResults',
'updater',
'updateAsync',
'updateAsync',
);
transformEnforcingEntityDeleteMethod(
j,
root,
'withAuthorizationResults',
'deleteAsync',
'deleteAsync',
);
// now do enforcing
transformEnforcingEntityChainMethod(
j,
root,
'enforcing',
'creator',
'enforceCreateAsync',
'createAsync',
);
transformEnforcingEntityChainMethod(
j,
root,
'enforcing',
'updater',
'enforceUpdateAsync',
'updateAsync',
);
transformEnforcingEntityDeleteMethod(j, root, 'enforcing', 'enforceDeleteAsync', 'deleteAsync');
// association loader
transformAssociationLoaderMethod(j, root);
return root.toSource();
}
@@ -0,0 +1,81 @@
import type { API, Collection, FileInfo, Options } from 'jscodeshift';
function transformWithAuthorizationResultsEntityChainMethod(
j: API['jscodeshift'],
root: Collection<any>,
): void {
// Find all entity expressions of the form
// `TestEntity.thing(viewerContext).withAuthorizationResults()`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: 'withAuthorizationResults',
},
},
})
.forEach((path) => {
const withAuthorizationResultsCallExpression = path.node; // TestEntity.thing(viewerContext).withAuthorizationResults()
const withAuthorizationResultsCallee = withAuthorizationResultsCallExpression.callee; // TestEntity.thing(viewerContext).withAuthorizationResults
if (withAuthorizationResultsCallee.type !== 'MemberExpression') {
return;
}
const thingCallExpression = withAuthorizationResultsCallee.object; // TestEntity.thing(viewerContext)
if (thingCallExpression.type !== 'CallExpression') {
return;
}
const thingCallee = thingCallExpression.callee; // TestEntity.thing
if (thingCallee.type !== 'MemberExpression') {
return;
}
if (thingCallee.property.type !== 'Identifier') {
return;
}
const newCreatorCalleeName = thingCallee.property.name + 'WithAuthorizationResults';
thingCallee.property.name = newCreatorCalleeName;
path.replace(thingCallExpression);
});
}
function transformEnforcingEntityChainMethod(j: API['jscodeshift'], root: Collection<any>): void {
// Find all entity expressions of the form
// `TestEntity.thing(viewerContext).enforcing()`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: 'enforcing',
},
},
})
.forEach((path) => {
const enforcingCallExpression = path.node; // TestEntity.thing(viewerContext).enforcing()
const enforcingCallee = enforcingCallExpression.callee; // TestEntity.thing(viewerContext).enforcing
if (enforcingCallee.type !== 'MemberExpression') {
return;
}
const thingCallExpression = enforcingCallee.object; // TestEntity.thing(viewerContext)
if (thingCallExpression.type !== 'CallExpression') {
return;
}
path.replace(thingCallExpression);
});
}
export default function transformer(file: FileInfo, api: API, _options: Options): string {
const j = api.jscodeshift;
const root = j.withParser('ts')(file.source);
transformWithAuthorizationResultsEntityChainMethod(j, root);
transformEnforcingEntityChainMethod(j, root);
return root.toSource();
}
@@ -0,0 +1,165 @@
import type { API, Collection, FileInfo, Options } from 'jscodeshift';
const KNEX_SPECIFIC_METHODS = [
'loadFirstByFieldEqualityConjunctionAsync',
'loadManyByFieldEqualityConjunctionAsync',
'loadManyByRawWhereClauseAsync',
];
function isKnexSpecificMethodUsed(j: API['jscodeshift'], node: any): boolean {
// Check if this loader call is followed by a knex-specific method
// We need to traverse the AST to find usages of the loader result
const parent = node.parent;
// Check if the loader call is directly chained with a knex method
if (parent?.value.type === 'MemberExpression' && parent.value.object === node.value) {
const grandParent = parent.parent;
if (grandParent?.value.type === 'CallExpression' && grandParent.value.callee === parent.value) {
if (
parent.value.property.type === 'Identifier' &&
KNEX_SPECIFIC_METHODS.includes(parent.value.property.name)
) {
return true;
}
}
}
// Check if the loader is assigned to a variable and then used with knex methods
if (parent?.value.type === 'VariableDeclarator' && parent.value.init === node.value) {
const variableName = parent.value.id.name;
const scope = parent.scope;
// Find all references to this variable in the same scope
const references = j(scope.path)
.find(j.Identifier, { name: variableName })
.filter((path) => {
// Check if this identifier is used as object in member expression
const parentNode = path.parent.value;
if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
const prop = parentNode.property;
if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
return true;
}
}
return false;
});
if (references.size() > 0) {
return true;
}
}
// Check await expressions
if (parent?.value.type === 'AwaitExpression' && parent.value.argument === node.value) {
const awaitParent = parent.parent;
if (awaitParent?.value.type === 'VariableDeclarator') {
const variableName = awaitParent.value.id.name;
const scope = awaitParent.scope;
// Find all references to this variable in the same scope
const references = j(scope.path)
.find(j.Identifier, { name: variableName })
.filter((path) => {
const parentNode = path.parent.value;
if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
const prop = parentNode.property;
if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
return true;
}
}
return false;
});
if (references.size() > 0) {
return true;
}
}
}
return false;
}
function transformLoaderToKnexLoader(j: API['jscodeshift'], root: Collection<any>): void {
// Find all entity expressions of the form `Entity.loader(viewerContext)`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: 'loader',
},
},
})
.forEach((path) => {
const loaderCallExpression = path.node; // Entity.loader(viewerContext)
const loaderCallee = loaderCallExpression.callee; // Entity.loader
if (loaderCallee.type !== 'MemberExpression') {
return;
}
// Make sure this is a static method call on an entity (not on an instance)
// Typically entity names start with uppercase letter
if (loaderCallee.object.type === 'Identifier') {
const firstChar = loaderCallee.object.name[0];
if (firstChar === firstChar?.toUpperCase()) {
// Check if this loader uses knex-specific methods
if (isKnexSpecificMethodUsed(j, path)) {
// Rename loader to knexLoader
if (loaderCallee.property.type === 'Identifier') {
loaderCallee.property.name = 'knexLoader';
}
}
}
}
});
}
function transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(
j: API['jscodeshift'],
root: Collection<any>,
): void {
// Find all entity expressions of the form `Entity.loaderWithAuthorizationResults(viewerContext)`
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
property: {
name: 'loaderWithAuthorizationResults',
},
},
})
.forEach((path) => {
const loaderCallExpression = path.node; // Entity.loaderWithAuthorizationResults(viewerContext)
const loaderCallee = loaderCallExpression.callee; // Entity.loaderWithAuthorizationResults
if (loaderCallee.type !== 'MemberExpression') {
return;
}
// Make sure this is a static method call on an entity (not on an instance)
// Typically entity names start with uppercase letter
if (loaderCallee.object.type === 'Identifier') {
const firstChar = loaderCallee.object.name[0];
if (firstChar === firstChar?.toUpperCase()) {
// Check if this loader uses knex-specific methods
if (isKnexSpecificMethodUsed(j, path)) {
// Rename loaderWithAuthorizationResults to knexLoaderWithAuthorizationResults
if (loaderCallee.property.type === 'Identifier') {
loaderCallee.property.name = 'knexLoaderWithAuthorizationResults';
}
}
}
}
});
}
export default function transformer(file: FileInfo, api: API, _options: Options): string {
const j = api.jscodeshift;
const root = j.withParser('ts')(file.source);
transformLoaderToKnexLoader(j, root);
transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(j, root);
return root.toSource();
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"],
"exclude": ["**/__testfixtures__/**"]
}
@@ -0,0 +1,80 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
### Bug Fixes
- declare runtime dependencies imported by published packages ([#717](https://github.com/expo/entity/issues/717)) ([3c96f3b](https://github.com/expo/entity/commit/3c96f3b9ea2b097c979447354f0abe35ed5e8d57))
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
### Features
- add forShare and skipLocked selection modifiers to knex loader ([#705](https://github.com/expo/entity/issues/705)) ([243a038](https://github.com/expo/entity/commit/243a0386fb9755f3926ade8dcc1b92adf38cd923))
- add forUpdate to knex loader ([#704](https://github.com/expo/entity/issues/704)) ([727f166](https://github.com/expo/entity/commit/727f16662d8baa3757bb8e94f51d837f17fd333d))
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-database-adapter-knex-testing-utils
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
### Bug Fixes
- move EntityPrivacyUtils back into core package ([#429](https://github.com/expo/entity/issues/429)) ([56ec27d](https://github.com/expo/entity/commit/56ec27d5f201d83930a2cc05dfc380a054bff1e1))
- pass in augmentable classes to installation ([#437](https://github.com/expo/entity/issues/437)) ([f5c728b](https://github.com/expo/entity/commit/f5c728be565e01c0a8ace9ef51f6de8f49184dab)), closes [/github.com/expo/entity/pull/410#pullrequestreview-3765893898](https://github.com//github.com/expo/entity/pull/410/issues/pullrequestreview-3765893898)
- refactor install method to instead be free functions ([#441](https://github.com/expo/entity/issues/441)) ([51d632d](https://github.com/expo/entity/commit/51d632da1619dc039d5c41ce6533f63254ec4337)), closes [#410](https://github.com/expo/entity/issues/410)
### Features
- add entity-database-adapter-knex-testing-utils containing StubPostgresDatabaseAdapter ([#412](https://github.com/expo/entity/issues/412)) ([eeccf59](https://github.com/expo/entity/commit/eeccf594c11bf090aa83bbf71fb4ead8668aec7d))
- add entityField SQL helper ([#481](https://github.com/expo/entity/issues/481)) ([459a5ff](https://github.com/expo/entity/commit/459a5ff85014a8c7396c0856dffacb1491ccb963))
- Add raw sqlfragment orderBy capability for pagination ([#457](https://github.com/expo/entity/issues/457)) ([0615ffa](https://github.com/expo/entity/commit/0615ffacffbe9e903a9bf03b171c102f0554b992))
- add support for nulls ordering in order by clauses ([#468](https://github.com/expo/entity/issues/468)) ([6c0838d](https://github.com/expo/entity/commit/6c0838dc577a3b6190e3551ee834925d7bd53bc6))
@@ -0,0 +1,3 @@
# @expo/entity-database-adapter-knex-testing-utils
Testing utilities for applications using Entity with Knex database adapter.
@@ -0,0 +1,46 @@
{
"name": "@expo/entity-database-adapter-knex-testing-utils",
"version": "0.72.0",
"description": "Testing utilities for applications using Entity with Knex database adapter",
"keywords": [
"entity"
],
"license": "MIT",
"author": "Expo",
"files": [
"build",
"!*.tsbuildinfo",
"!__*",
"src"
],
"type": "module",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"scripts": {
"build": "tsc --build",
"prepack": "rm -rf build && yarn build",
"clean": "yarn build --clean",
"lint": "yarn run --top-level oxlint --type-aware src",
"lint-fix": "yarn run lint --fix",
"test": "yarn test:all --rootDir $(pwd)"
},
"dependencies": {
"@expo/entity": "workspace:^",
"@expo/entity-database-adapter-knex": "workspace:^",
"@expo/entity-testing-utils": "workspace:^",
"invariant": "^2.2.4",
"uuid": "^14.0.0"
},
"devDependencies": {
"@jest/globals": "30.5.2",
"@types/invariant": "2.2.37",
"@types/node": "24.19.2",
"typescript": "6.0.3"
},
"peerDependencies": {
"@jest/globals": "*"
},
"engines": {
"node": ">=18"
}
}
@@ -0,0 +1,323 @@
import type { EntityConfiguration, FieldTransformerMap } from '@expo/entity';
import {
computeIfAbsent,
getDatabaseFieldForEntityField,
IntField,
mapMap,
StringField,
transformFieldsToDatabaseObject,
} from '@expo/entity';
import type {
SQLFragment,
TableFieldMultiValueEqualityCondition,
TableFieldSingleValueEqualityCondition,
TableOrderByClause,
TableQuerySelectionModifiers,
} from '@expo/entity-database-adapter-knex';
import {
BasePostgresEntityDatabaseAdapter,
NullsOrdering,
OrderByOrdering,
} from '@expo/entity-database-adapter-knex';
import invariant from 'invariant';
import { v7 as uuidv7 } from 'uuid';
export class StubPostgresDatabaseAdapter<
TFields extends Record<string, any>,
TIDField extends keyof TFields,
> extends BasePostgresEntityDatabaseAdapter<TFields, TIDField> {
constructor(
private readonly entityConfiguration2: EntityConfiguration<TFields, TIDField>,
private readonly dataStore: Map<string, Readonly<{ [key: string]: any }>[]>,
) {
super(entityConfiguration2);
}
public static convertFieldObjectsToDataStore<
TFields extends Record<string, any>,
TIDField extends keyof TFields,
>(
entityConfiguration: EntityConfiguration<TFields, TIDField>,
dataStore: Map<string, Readonly<TFields>[]>,
): Map<string, Readonly<{ [key: string]: any }>[]> {
return mapMap(dataStore, (objectsForTable) =>
objectsForTable.map((objectForTable) =>
transformFieldsToDatabaseObject(entityConfiguration, new Map(), objectForTable),
),
);
}
public getObjectCollectionForTable(tableName: string): { [key: string]: any }[] {
return computeIfAbsent(this.dataStore, tableName, () => []);
}
protected getFieldTransformerMap(): FieldTransformerMap {
return new Map();
}
private static uniqBy<T>(a: T[], keyExtractor: (k: T) => string): T[] {
const seen = new Set();
return a.filter((item) => {
const k = keyExtractor(item);
if (seen.has(k)) {
return false;
}
seen.add(k);
return true;
});
}
protected async fetchManyWhereInternalAsync(
_queryInterface: any,
tableName: string,
tableColumns: readonly string[],
tableTuples: (readonly any[])[],
): Promise<object[]> {
const objectCollection = this.getObjectCollectionForTable(tableName);
const results = StubPostgresDatabaseAdapter.uniqBy(tableTuples, (tuple) =>
JSON.stringify(tuple),
).reduce(
(acc, tableTuple) => {
return acc.concat(
objectCollection.filter((obj) => {
return tableColumns.every((tableColumn, index) => {
return obj[tableColumn] === tableTuple[index];
});
}),
);
},
[] as { [key: string]: any }[],
);
return [...results];
}
protected async fetchOneWhereInternalAsync(
queryInterface: any,
tableName: string,
tableColumns: readonly string[],
tableTuple: readonly any[],
): Promise<object | null> {
const results = await this.fetchManyWhereInternalAsync(
queryInterface,
tableName,
tableColumns,
[tableTuple],
);
return results[0] ?? null;
}
private static compareByOrderBys<TFields extends Record<string, any>>(
orderBys: TableOrderByClause<TFields>[],
objectA: { [key: string]: any },
objectB: { [key: string]: any },
): 0 | 1 | -1 {
if (orderBys.length === 0) {
return 0;
}
const currentOrderBy = orderBys[0]!;
if (!('columnName' in currentOrderBy)) {
throw new Error('SQL fragment order by not supported for StubDatabaseAdapter');
}
const aField = objectA[currentOrderBy.columnName];
const bField = objectB[currentOrderBy.columnName];
// Determine effective nulls ordering:
// - If explicitly set, use that
// - Otherwise use PostgreSQL defaults: NULLS LAST for ASC, NULLS FIRST for DESC
const nullsFirst =
currentOrderBy.nulls !== undefined
? currentOrderBy.nulls === NullsOrdering.FIRST
: currentOrderBy.order === OrderByOrdering.DESCENDING;
if (aField === null && bField === null) {
return this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
} else if (aField === null) {
return nullsFirst ? -1 : 1;
} else if (bField === null) {
return nullsFirst ? 1 : -1;
}
switch (currentOrderBy.order) {
case OrderByOrdering.DESCENDING: {
return aField > bField
? -1
: aField < bField
? 1
: this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
}
case OrderByOrdering.ASCENDING: {
return bField > aField
? -1
: bField < aField
? 1
: this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
}
}
}
protected async fetchManyByFieldEqualityConjunctionInternalAsync(
_queryInterface: any,
tableName: string,
tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
): Promise<object[]> {
let filteredObjects = this.getObjectCollectionForTable(tableName);
for (const { tableField, tableValue } of tableFieldSingleValueEqualityOperands) {
filteredObjects = filteredObjects.filter((obj) => obj[tableField] === tableValue);
}
for (const { tableField, tableValues } of tableFieldMultiValueEqualityOperands) {
filteredObjects = filteredObjects.filter((obj) => tableValues.includes(obj[tableField]));
}
const orderBy = querySelectionModifiers.orderBy;
if (orderBy !== undefined) {
filteredObjects = filteredObjects.sort((a, b) =>
StubPostgresDatabaseAdapter.compareByOrderBys(orderBy, a, b),
);
}
const offset = querySelectionModifiers.offset;
if (offset !== undefined) {
filteredObjects = filteredObjects.slice(offset);
}
const limit = querySelectionModifiers.limit;
if (limit !== undefined) {
filteredObjects = filteredObjects.slice(0, 0 + limit);
}
return filteredObjects;
}
protected fetchManyBySQLFragmentInternalAsync(
_queryInterface: any,
_tableName: string,
_sqlFragment: SQLFragment<TFields>,
_querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
): Promise<object[]> {
throw new Error('SQL fragments not supported for StubDatabaseAdapter');
}
protected async countByFieldEqualityConjunctionInternalAsync(
queryInterface: any,
tableName: string,
tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
): Promise<number> {
const results = await this.fetchManyByFieldEqualityConjunctionInternalAsync(
queryInterface,
tableName,
tableFieldSingleValueEqualityOperands,
tableFieldMultiValueEqualityOperands,
{
orderBy: undefined,
offset: undefined,
limit: undefined,
forUpdate: undefined,
forShare: undefined,
skipLocked: undefined,
},
);
return results.length;
}
protected countBySQLFragmentInternalAsync(
_queryInterface: any,
_tableName: string,
_sqlFragment: SQLFragment<TFields>,
): Promise<number> {
throw new Error('SQL fragment count not supported for StubDatabaseAdapter');
}
private generateRandomID(): any {
const idSchemaField = this.entityConfiguration2.schema.get(this.entityConfiguration2.idField);
invariant(
idSchemaField,
`No schema field found for ${String(this.entityConfiguration2.idField)}`,
);
if (idSchemaField instanceof StringField) {
return uuidv7();
} else if (idSchemaField instanceof IntField) {
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
} else {
throw new Error(
`Unsupported ID type for StubPostgresDatabaseAdapter: ${idSchemaField.constructor.name}`,
);
}
}
protected async insertInternalAsync(
_queryInterface: any,
tableName: string,
object: object,
): Promise<object[]> {
const objectCollection = this.getObjectCollectionForTable(tableName);
const idField = getDatabaseFieldForEntityField(
this.entityConfiguration2,
this.entityConfiguration2.idField,
);
const objectToInsert = {
[idField]: this.generateRandomID(),
...object,
};
objectCollection.push(objectToInsert);
return [objectToInsert];
}
protected async updateInternalAsync(
_queryInterface: any,
tableName: string,
tableIdField: string,
id: any,
object: object,
): Promise<{ updatedRowCount: number }> {
// SQL does not support empty updates, mirror behavior here for better test simulation
if (Object.keys(object).length === 0) {
throw new Error(`Empty update (${tableIdField} = ${id})`);
}
const objectCollection = this.getObjectCollectionForTable(tableName);
const objectIndex = objectCollection.findIndex((obj) => {
return obj[tableIdField] === id;
});
// SQL updates to a nonexistent row succeed but affect 0 rows,
// mirror that behavior here for better test simulation
if (objectIndex < 0) {
return { updatedRowCount: 0 };
}
objectCollection[objectIndex] = {
...objectCollection[objectIndex],
...object,
};
return { updatedRowCount: 1 };
}
protected async deleteInternalAsync(
_queryInterface: any,
tableName: string,
tableIdField: string,
id: any,
): Promise<number> {
const objectCollection = this.getObjectCollectionForTable(tableName);
const objectIndex = objectCollection.findIndex((obj) => {
return obj[tableIdField] === id;
});
// SQL deletes to a nonexistent row succeed and affect 0 rows,
// mirror that behavior here for better test simulation
if (objectIndex < 0) {
return 0;
}
objectCollection.splice(objectIndex, 1);
return 1;
}
}
@@ -0,0 +1,17 @@
import type {
EntityConfiguration,
EntityDatabaseAdapter,
IEntityDatabaseAdapterProvider,
} from '@expo/entity';
import { StubPostgresDatabaseAdapter } from './StubPostgresDatabaseAdapter.ts';
export class StubPostgresDatabaseAdapterProvider implements IEntityDatabaseAdapterProvider {
private readonly objectCollection = new Map();
getDatabaseAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
entityConfiguration: EntityConfiguration<TFields, TIDField>,
): EntityDatabaseAdapter<TFields, TIDField> {
return new StubPostgresDatabaseAdapter(entityConfiguration, this.objectCollection);
}
}
@@ -0,0 +1,61 @@
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
DateField,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
} from '@expo/entity';
export type DateIDTestFields = {
id: Date;
};
export const dateIDTestEntityConfiguration = new EntityConfiguration<DateIDTestFields, 'id'>({
idField: 'id',
tableName: 'simple_test_entity_should_not_write_to_db',
schema: {
id: new DateField({
columnName: 'custom_id',
cache: true,
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
});
export class DateIDTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
DateIDTestFields,
'id',
ViewerContext,
DateIDTestEntity
> {
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
];
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
];
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
];
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
];
}
export class DateIDTestEntity extends Entity<DateIDTestFields, 'id', ViewerContext> {
static defineCompanionDefinition(): EntityCompanionDefinition<
DateIDTestFields,
'id',
ViewerContext,
DateIDTestEntity,
DateIDTestEntityPrivacyPolicy
> {
return {
entityClass: DateIDTestEntity,
entityConfiguration: dateIDTestEntityConfiguration,
privacyPolicyClass: DateIDTestEntityPrivacyPolicy,
};
}
}
@@ -1,10 +1,11 @@
import Entity from '../Entity';
import { EntityCompanionDefinition } from '../EntityCompanionProvider';
import EntityConfiguration from '../EntityConfiguration';
import { UUIDField } from '../EntityFields';
import EntityPrivacyPolicy from '../EntityPrivacyPolicy';
import ViewerContext from '../ViewerContext';
import AlwaysAllowPrivacyPolicyRule from '../rules/AlwaysAllowPrivacyPolicyRule';
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
UUIDField,
} from '@expo/entity';
export type SimpleTestFields = {
id: string;
@@ -12,12 +13,13 @@ export type SimpleTestFields = {
export type SimpleTestFieldSelection = keyof SimpleTestFields;
export const simpleTestEntityConfiguration = new EntityConfiguration<SimpleTestFields>({
export const simpleTestEntityConfiguration = new EntityConfiguration<SimpleTestFields, 'id'>({
idField: 'id',
tableName: 'simple_test_entity_should_not_write_to_db',
schema: {
id: new UUIDField({
columnName: 'custom_id',
cache: true,
}),
},
databaseAdapterFlavor: 'postgres',
@@ -26,7 +28,7 @@ export const simpleTestEntityConfiguration = new EntityConfiguration<SimpleTestF
export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestFieldSelection
@@ -34,7 +36,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestFieldSelection
@@ -43,7 +45,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestFieldSelection
@@ -52,7 +54,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestFieldSelection
@@ -61,7 +63,7 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestFieldSelection
@@ -69,26 +71,24 @@ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
];
}
export default class SimpleTestEntity extends Entity<
export class SimpleTestEntity extends Entity<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestFieldSelection
> {
static getCompanionDefinition(): EntityCompanionDefinition<
static defineCompanionDefinition(): EntityCompanionDefinition<
SimpleTestFields,
string,
'id',
ViewerContext,
SimpleTestEntity,
SimpleTestEntityPrivacyPolicy,
SimpleTestFieldSelection
> {
return testEntityCompanion;
return {
entityClass: SimpleTestEntity,
entityConfiguration: simpleTestEntityConfiguration,
privacyPolicyClass: SimpleTestEntityPrivacyPolicy,
};
}
}
export const testEntityCompanion = new EntityCompanionDefinition({
entityClass: SimpleTestEntity,
entityConfiguration: simpleTestEntityConfiguration,
privacyPolicyClass: SimpleTestEntityPrivacyPolicy,
});
@@ -0,0 +1,130 @@
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
DateField,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
IntField,
StringField,
UUIDField,
} from '@expo/entity';
import type { Result } from '@expo/results';
import { result } from '@expo/results';
export type TestFields = {
customIdField: string;
testIndexedField: string;
stringField: string;
intField: number;
dateField: Date;
nullableField: string | null;
};
export const testEntityConfiguration = new EntityConfiguration<TestFields, 'customIdField'>({
idField: 'customIdField',
tableName: 'test_entity_should_not_write_to_db',
schema: {
customIdField: new UUIDField({
columnName: 'custom_id',
cache: true,
}),
testIndexedField: new StringField({
columnName: 'test_index',
cache: true,
}),
stringField: new StringField({
columnName: 'string_field',
}),
intField: new IntField({
columnName: 'number_field',
}),
dateField: new DateField({
columnName: 'date_field',
}),
nullableField: new StringField({
columnName: 'nullable_field',
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
compositeFieldDefinitions: [
{ compositeField: ['stringField', 'intField'], cache: false },
{ compositeField: ['stringField', 'testIndexedField'], cache: true },
{ compositeField: ['nullableField', 'testIndexedField'], cache: true },
],
});
export class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<
TestFields,
'customIdField',
ViewerContext,
TestEntity
> {
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
];
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
];
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
];
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
];
}
export class TestEntity extends Entity<TestFields, 'customIdField', ViewerContext> {
static defineCompanionDefinition(): EntityCompanionDefinition<
TestFields,
'customIdField',
ViewerContext,
TestEntity,
TestEntityPrivacyPolicy
> {
return {
entityClass: TestEntity,
entityConfiguration: testEntityConfiguration,
privacyPolicyClass: TestEntityPrivacyPolicy,
};
}
getBlah(): string {
return 'Hello World!';
}
static async helloAsync(
viewerContext: ViewerContext,
testValue: string,
): Promise<Result<TestEntity>> {
const fields = {
customIdField: testValue,
testIndexedField: 'hello',
stringField: 'hello',
intField: 1,
dateField: new Date(),
nullableField: null,
};
return result(
new TestEntity({
viewerContext,
id: testValue,
databaseFields: fields,
selectedFields: fields,
}),
);
}
static async returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
return result(new Error('return entity'));
}
static async throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
throw new Error('threw entity');
}
static async nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string> {
return testValue;
}
}
@@ -0,0 +1,61 @@
import type { EntityCompanionDefinition, ViewerContext } from '@expo/entity';
import {
AlwaysAllowPrivacyPolicyRule,
Entity,
EntityConfiguration,
EntityPrivacyPolicy,
IntField,
} from '@expo/entity';
export type NumberKeyFields = {
id: number;
};
export const numberKeyEntityConfiguration = new EntityConfiguration<NumberKeyFields, 'id'>({
idField: 'id',
tableName: 'simple_test_entity_should_not_write_to_db',
schema: {
id: new IntField({
columnName: 'custom_id',
cache: false,
}),
},
databaseAdapterFlavor: 'postgres',
cacheAdapterFlavor: 'redis',
});
export class NumberKeyPrivacyPolicy extends EntityPrivacyPolicy<
NumberKeyFields,
'id',
ViewerContext,
NumberKeyEntity
> {
protected override readonly readRules = [
new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
];
protected override readonly createRules = [
new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
];
protected override readonly updateRules = [
new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
];
protected override readonly deleteRules = [
new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
];
}
export class NumberKeyEntity extends Entity<NumberKeyFields, 'id', ViewerContext> {
static defineCompanionDefinition(): EntityCompanionDefinition<
NumberKeyFields,
'id',
ViewerContext,
NumberKeyEntity,
NumberKeyPrivacyPolicy
> {
return {
entityClass: NumberKeyEntity,
entityConfiguration: numberKeyEntityConfiguration,
privacyPolicyClass: NumberKeyPrivacyPolicy,
};
}
}
@@ -0,0 +1,32 @@
import { expect, it } from '@jest/globals';
import { readFile } from 'fs/promises';
it.each([
'StubPostgresDatabaseAdapter',
'StubPostgresDatabaseAdapterProvider',
'createUnitTestPostgresEntityCompanionProvider',
])('%s is the same as in @expo/entity-database-adapter-knex', async (fileName) => {
// These stub adapters need to be shared for testing, but we can't have them in the main
// entity-database-adapter-knex package since they would be exposed in production.
// Therefore, we duplicate them and ensure they stay in sync.
const fileContentsFromEntityDatabaseAdapterKnex = await readFile(
new URL(
`../../../entity-database-adapter-knex/src/__tests__/fixtures/${fileName}.ts`,
import.meta.url,
),
'utf-8',
);
const fileContentsFromTestingUtils = await readFile(
new URL(`../${fileName}.ts`, import.meta.url),
'utf-8',
);
const trimmedFiles = [
fileContentsFromEntityDatabaseAdapterKnex,
fileContentsFromTestingUtils,
].map((file) => file.substring(file.indexOf('export')));
expect(trimmedFiles[0]?.length).toBeGreaterThan(0);
expect(trimmedFiles[0]).toEqual(trimmedFiles[1]);
});
@@ -0,0 +1,16 @@
import { EntityCompanionProvider, ViewerContext } from '@expo/entity';
import { describe, expect, it } from '@jest/globals';
import { TestEntity } from '../__testfixtures__/TestEntity.ts';
import { createUnitTestPostgresEntityCompanionProvider } from '../createUnitTestPostgresEntityCompanionProvider.ts';
describe(createUnitTestPostgresEntityCompanionProvider, () => {
it('creates a new EntityCompanionProvider', async () => {
const companionProvider = createUnitTestPostgresEntityCompanionProvider();
expect(companionProvider).toBeInstanceOf(EntityCompanionProvider);
const viewerContext = new ViewerContext(companionProvider);
await expect(TestEntity.creator(viewerContext).createAsync()).resolves.toBeInstanceOf(
TestEntity,
);
});
});
@@ -0,0 +1,39 @@
import type { IEntityMetricsAdapter } from '@expo/entity';
import { EntityCompanionProvider, NoOpEntityMetricsAdapter } from '@expo/entity';
import {
InMemoryFullCacheStubCacheAdapterProvider,
StubQueryContextProvider,
} from '@expo/entity-testing-utils';
import { StubPostgresDatabaseAdapterProvider } from './StubPostgresDatabaseAdapterProvider.ts';
const queryContextProvider = new StubQueryContextProvider();
/**
* Entity companion provider for use in knex unit tests. All database and cache implementations
* are replaced with in-memory simulations.
*/
export const createUnitTestPostgresEntityCompanionProvider = (
metricsAdapter: IEntityMetricsAdapter = new NoOpEntityMetricsAdapter(),
): EntityCompanionProvider => {
return new EntityCompanionProvider(
metricsAdapter,
new Map([
[
'postgres',
{
adapterProvider: new StubPostgresDatabaseAdapterProvider(),
queryContextProvider,
},
],
]),
new Map([
[
'redis',
{
cacheAdapterProvider: new InMemoryFullCacheStubCacheAdapterProvider(),
},
],
]),
);
};
@@ -0,0 +1,9 @@
/* eslint-disable tsdoc/syntax */
/**
* @packageDocumentation
* @module @expo/entity-database-adapter-knex-testing-utils
*/
export * from './createUnitTestPostgresEntityCompanionProvider.ts';
export * from './StubPostgresDatabaseAdapter.ts';
export * from './StubPostgresDatabaseAdapterProvider.ts';
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"],
"references": [{ "path": "../entity" }, { "path": "../entity-database-adapter-knex" }]
}
+273 -175
View File
@@ -3,341 +3,439 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
# [0.72.0](https://github.com/expo/entity/compare/v0.71.0...v0.72.0) (2026-10-29)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.71.0](https://github.com/expo/entity/compare/v0.70.0...v0.71.0) (2026-10-09)
### Bug Fixes
- declare runtime dependencies imported by published packages ([#717](https://github.com/expo/entity/issues/717)) ([3c96f3b](https://github.com/expo/entity/commit/3c96f3b9ea2b097c979447354f0abe35ed5e8d57))
# [0.70.0](https://github.com/expo/entity/compare/v0.69.0...v0.70.0) (2026-10-05)
### Features
* add BigIntField ([#193](https://github.com/expo/entity/issues/193)) ([acbad82](https://github.com/expo/entity/commit/acbad826a37bb5a054b4dcf9be91d11aa5f7f29a))
* nested transactions ([#194](https://github.com/expo/entity/issues/194)) ([a77b914](https://github.com/expo/entity/commit/a77b914ad6dc018d1e309c9caf4971088a553ecf))
- add transient error for PostgreSQL lock timeouts ([#716](https://github.com/expo/entity/issues/716)) ([13ce74e](https://github.com/expo/entity/commit/13ce74e93aec0a0b0b84edd1e1e89d785a3dbf46)), closes [#714](https://github.com/expo/entity/issues/714) [#715](https://github.com/expo/entity/issues/715)
# [0.69.0](https://github.com/expo/entity/compare/v0.68.0...v0.69.0) (2026-09-29)
### Features
- add ID-based and field-based knex database loaders for use with locking modifiers ([#706](https://github.com/expo/entity/issues/706)) ([e87e2a3](https://github.com/expo/entity/commit/e87e2a3a28f4279f5911bfd0189964b94a2ced7f))
# [0.68.0](https://github.com/expo/entity/compare/v0.67.0...v0.68.0) (2026-09-25)
### Features
- add forShare and skipLocked selection modifiers to knex loader ([#705](https://github.com/expo/entity/issues/705)) ([243a038](https://github.com/expo/entity/commit/243a0386fb9755f3926ade8dcc1b92adf38cd923))
- add forUpdate to knex loader ([#704](https://github.com/expo/entity/issues/704)) ([727f166](https://github.com/expo/entity/commit/727f16662d8baa3757bb8e94f51d837f17fd333d))
# [0.67.0](https://github.com/expo/entity/compare/v0.66.0...v0.67.0) (2026-09-18)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.66.0](https://github.com/expo/entity/compare/v0.65.0...v0.66.0) (2026-08-03)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.65.0](https://github.com/expo/entity/compare/v0.64.0...v0.65.0) (2026-07-10)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.62.0](https://github.com/expo/entity/compare/v0.61.0...v0.62.0) (2026-03-16)
### Bug Fixes
- only throw EntityDatabaseAdapterPaginationCursorInvalidError from cursor decoding logic ([#537](https://github.com/expo/entity/issues/537)) ([e3cee07](https://github.com/expo/entity/commit/e3cee07091af8d1e6c5bb2d82f13e837c7658ea9))
# [0.61.0](https://github.com/expo/entity/compare/v0.60.0...v0.61.0) (2026-03-12)
### Features
- update the SQLExpression and SQLFragmentHelpers APIs ([#516](https://github.com/expo/entity/issues/516)) ([112d4dc](https://github.com/expo/entity/commit/112d4dc9b379da5317ff6acaf1d2e9bd597128a6))
# [0.60.0](https://github.com/expo/entity/compare/v0.59.0...v0.60.0) (2026-03-06)
### Features
- Add SQLExpression class that supports fluent comparison method API ([#506](https://github.com/expo/entity/issues/506)) ([535b681](https://github.com/expo/entity/commit/535b6811e08578aba1aadb095f3e401cfe7eaa26))
# [0.59.0](https://github.com/expo/entity/compare/v0.58.0...v0.59.0) (2026-03-03)
### Features
- add support to SQLFragment for `= ANY(?)` ([#498](https://github.com/expo/entity/issues/498)) ([b931daa](https://github.com/expo/entity/commit/b931daa9a082cdb33f4559d6d32e1655f0459b79))
# [0.58.0](https://github.com/expo/entity/compare/v0.57.0...v0.58.0) (2026-03-02)
### Bug Fixes
- fix some security review suggestions ([#485](https://github.com/expo/entity/issues/485)) ([e263bd2](https://github.com/expo/entity/commit/e263bd218cda579365766d0918c4961304bd1d9f))
# [0.57.0](https://github.com/expo/entity/compare/v0.56.0...v0.57.0) (2026-02-28)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.56.0](https://github.com/expo/entity/compare/v0.55.0...v0.56.0) (2026-02-28)
### Bug Fixes
- Add default for TSelectedFields for application-instantiated types ([#438](https://github.com/expo/entity/issues/438)) ([3d4aa0e](https://github.com/expo/entity/commit/3d4aa0e9efa519a36f3db69bd8f117a993c100fa))
- Apply same viewer context subclass type mechanism to knexLoader and related methods ([#472](https://github.com/expo/entity/issues/472)) ([018a40b](https://github.com/expo/entity/commit/018a40bc01d579ffbf88f308e641921e6fc39097))
- correct discriminated union type for forward/backward pagination ([#474](https://github.com/expo/entity/issues/474)) ([90ae6e2](https://github.com/expo/entity/commit/90ae6e299bda26ac9a80d83e83334d4152c022dd))
- correct pagination behavior for standard pagination with descending ordering clauses ([#477](https://github.com/expo/entity/issues/477)) ([e32c4c2](https://github.com/expo/entity/commit/e32c4c2bcc05f225a556f95c90f1ee0f4e24de74))
- correct precedence parentheses for SQLFragment joins ([#476](https://github.com/expo/entity/issues/476)) ([2c4c649](https://github.com/expo/entity/commit/2c4c649a4bca5bc9ffdc41eb3135b2dfe4db5e57)), closes [/github.com/expo/entity/blob/main/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts#L303](https://github.com//github.com/expo/entity/blob/main/packages/entity-database-adapter-knex/src/internal/EntityKnexDataManager.ts/issues/L303)
- eliminate some unnecessary any casts ([#448](https://github.com/expo/entity/issues/448)) ([cc78029](https://github.com/expo/entity/commit/cc78029117c14510b0934b5e0d19876a45570f8d))
- explicitly document behavior when cursor entity no longer exists ([#453](https://github.com/expo/entity/issues/453)) ([4dc156c](https://github.com/expo/entity/commit/4dc156c7a8dc50bc9e2802be133b9e258b289f54)), closes [#422](https://github.com/expo/entity/issues/422) [#431](https://github.com/expo/entity/issues/431)
- move EntityPrivacyUtils back into core package ([#429](https://github.com/expo/entity/issues/429)) ([56ec27d](https://github.com/expo/entity/commit/56ec27d5f201d83930a2cc05dfc380a054bff1e1))
- pass in augmentable classes to installation ([#437](https://github.com/expo/entity/issues/437)) ([f5c728b](https://github.com/expo/entity/commit/f5c728be565e01c0a8ace9ef51f6de8f49184dab)), closes [/github.com/expo/entity/pull/410#pullrequestreview-3765893898](https://github.com//github.com/expo/entity/pull/410/issues/pullrequestreview-3765893898)
- refactor install method to instead be free functions ([#441](https://github.com/expo/entity/issues/441)) ([51d632d](https://github.com/expo/entity/commit/51d632da1619dc039d5c41ce6533f63254ec4337)), closes [#410](https://github.com/expo/entity/issues/410)
- update integration test to use BlahEntity.knexLoader syntax ([#444](https://github.com/expo/entity/issues/444)) ([457f58a](https://github.com/expo/entity/commit/457f58ad2c4e1cf09f3132f3b9af5008ba0290c7)), closes [#441](https://github.com/expo/entity/issues/441)
- Use TSelectedFields for knex loader order by method ([#424](https://github.com/expo/entity/issues/424)) ([a9d09f4](https://github.com/expo/entity/commit/a9d09f42b67378d06133bcfb5b99c9afb17aac0d))
### Features
- add entity-database-adapter-knex-testing-utils containing StubPostgresDatabaseAdapter ([#412](https://github.com/expo/entity/issues/412)) ([eeccf59](https://github.com/expo/entity/commit/eeccf594c11bf090aa83bbf71fb4ead8668aec7d))
- add entityField SQL helper ([#481](https://github.com/expo/entity/issues/481)) ([459a5ff](https://github.com/expo/entity/commit/459a5ff85014a8c7396c0856dffacb1491ccb963))
- Add ilike and trigram similarity search to pagination ([#431](https://github.com/expo/entity/issues/431)) ([f72cbca](https://github.com/expo/entity/commit/f72cbcaf5a722e6bb8299644ec32b07f1d3362a9)), closes [#422](https://github.com/expo/entity/issues/422)
- add method to get pagination cursor for single entity ([#475](https://github.com/expo/entity/issues/475)) ([898f71c](https://github.com/expo/entity/commit/898f71ce082c2593585575b60bd6ef2546717124))
- Add paginated loader to entity-database-adapter-knex ([#422](https://github.com/expo/entity/issues/422)) ([58deffd](https://github.com/expo/entity/commit/58deffd426b0214073ede608cd47c04db2596d60))
- Add pagination max page size configuration ([#436](https://github.com/expo/entity/issues/436)) ([3ec4b5f](https://github.com/expo/entity/commit/3ec4b5f0cd53bf18d6bcfa27dd29b70d3f4d5f19))
- add PostgresEntity/ReadonlyPostgresEntity classes that expose knexLoader methods ([#442](https://github.com/expo/entity/issues/442)) ([40b409f](https://github.com/expo/entity/commit/40b409f6f889febf8b90b8837caecac26ff64174)), closes [#441](https://github.com/expo/entity/issues/441)
- Add raw sqlfragment orderBy capability for pagination ([#457](https://github.com/expo/entity/issues/457)) ([0615ffa](https://github.com/expo/entity/commit/0615ffacffbe9e903a9bf03b171c102f0554b992))
- add support for nulls ordering in order by clauses ([#468](https://github.com/expo/entity/issues/468)) ([6c0838d](https://github.com/expo/entity/commit/6c0838dc577a3b6190e3551ee834925d7bd53bc6))
- support nullable search fields with postgres transforms ([#470](https://github.com/expo/entity/issues/470)) ([53b0604](https://github.com/expo/entity/commit/53b0604152b8ca5ad9fc616d18478f93473c7eb0))
# [0.55.0](https://github.com/expo/entity/compare/v0.54.0...v0.55.0) (2026-01-27)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.54.0](https://github.com/expo/entity/compare/v0.53.0...v0.54.0) (2026-01-21)
### Bug Fixes
- **deps:** pin dependencies ([#328](https://github.com/expo/entity/issues/328)) ([5679f27](https://github.com/expo/entity/commit/5679f27209f1515ca5e626d858c4aa054b414625))
# [0.53.0](https://github.com/expo/entity/compare/v0.52.0...v0.53.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.52.0](https://github.com/expo/entity/compare/v0.51.0...v0.52.0) (2025-12-19)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.51.0](https://github.com/expo/entity/compare/v0.50.0...v0.51.0) (2025-11-21)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.50.0](https://github.com/expo/entity/compare/v0.49.0...v0.50.0) (2025-10-07)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.49.0](https://github.com/expo/entity/compare/v0.48.0...v0.49.0) (2025-10-07)
### Features
- throw specific errors for database adapter issues ([#311](https://github.com/expo/entity/issues/311)) ([f6639f9](https://github.com/expo/entity/commit/f6639f9def4ac20b628dd48fdca8d7cf61b6347b))
# [0.48.0](https://github.com/expo/entity/compare/v0.47.0...v0.48.0) (2025-09-19)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.47.0](https://github.com/expo/entity/compare/v0.46.0...v0.47.0) (2025-09-19)
### Features
- add support for Buffer fields ([#305](https://github.com/expo/entity/issues/305)) ([31b52a5](https://github.com/expo/entity/commit/31b52a5f75842f4810f66c12ad03a10d9192f889))
# [0.46.0](https://github.com/expo/entity/compare/v0.45.0...v0.46.0) (2025-06-26)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.45.0](https://github.com/expo/entity/compare/v0.44.0...v0.45.0) (2025-06-06)
### Features
- add createOrGetExistingAsync and createWithUniqueConstraintRecoveryAsync utility methods ([#291](https://github.com/expo/entity/issues/291)) ([78bc264](https://github.com/expo/entity/commit/78bc264b3328692b4b6722388a57d298f02e65c5))
# [0.44.0](https://github.com/expo/entity/compare/v0.43.0...v0.44.0) (2025-05-29)
### Bug Fixes
- introduce transactional dataloader mode and fix global transaction ID generation ([#289](https://github.com/expo/entity/issues/289)) ([d3e89bd](https://github.com/expo/entity/commit/d3e89bdd82e6df5c5b35522bc3574fd9e1cfddff))
### Features
- add transaction-scoped dataloaders ([#284](https://github.com/expo/entity/issues/284)) ([e054a0d](https://github.com/expo/entity/commit/e054a0d74b3a39158ca7d73d05f59d3633113400)), closes [#98](https://github.com/expo/entity/issues/98) [#194](https://github.com/expo/entity/issues/194)
# [0.43.0](https://github.com/expo/entity/compare/v0.42.0...v0.43.0) (2025-04-10)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.42.0](https://github.com/expo/entity/compare/v0.41.0...v0.42.0) (2025-04-10)
### Features
- add @expo/entity-testing-utils package ([#280](https://github.com/expo/entity/issues/280)) ([485894a](https://github.com/expo/entity/commit/485894af16e233d533a9480285bbda56812cbe0b))
- add composite field loading and caching ([#272](https://github.com/expo/entity/issues/272)) ([f0aa0da](https://github.com/expo/entity/commit/f0aa0dafebdb56418cbd22dda437233d850db3e7))
- change entity id field generic to field name and derive type where necessary ([#278](https://github.com/expo/entity/issues/278)) ([b7e524c](https://github.com/expo/entity/commit/b7e524c41892797608d0b884410b8d520a80a9ef))
- convert batched/cached loader interface to holder pattern ([#271](https://github.com/expo/entity/issues/271)) ([06b3cb7](https://github.com/expo/entity/commit/06b3cb7f8076ef9e5b83cdf154da159801bd14cc)), closes [#201](https://github.com/expo/entity/issues/201)
- enforce explicit id field cache property ([#276](https://github.com/expo/entity/issues/276)) ([1da5cc0](https://github.com/expo/entity/commit/1da5cc01e1affc3b3338a0ab2050504b5853ac6f))
# [0.41.0](https://github.com/expo/entity/compare/v0.40.0...v0.41.0) (2025-03-07)
- feat!: Default all loaders and mutators to enforcing (#256) ([a976e76](https://github.com/expo/entity/commit/a976e765098bca27049585e43a3bed5c71b9ac6b)), closes [#256](https://github.com/expo/entity/issues/256)
### BREAKING CHANGES
- Default all loaders and mutators to enforcing
# [0.40.0](https://github.com/expo/entity/compare/v0.39.0...v0.40.0) (2025-02-27)
### Reverts
- Revert "v0.40.0" ([fa78ce8](https://github.com/expo/entity/commit/fa78ce84e2f2eb8199e36fd51779fc68905001c0))
# [0.39.0](https://github.com/expo/entity/compare/v0.38.0...v0.39.0) (2024-11-21)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.38.0](https://github.com/expo/entity/compare/v0.37.0...v0.38.0) (2024-06-27)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.37.0](https://github.com/expo/entity/compare/v0.36.0...v0.37.0) (2024-06-12)
### Bug Fixes
- update barrels ([#241](https://github.com/expo/entity/issues/241)) ([8c5f81b](https://github.com/expo/entity/commit/8c5f81bcca107e9b7bbea5f0ab41b0105057806e))
# [0.36.0](https://github.com/expo/entity/compare/v0.35.0...v0.36.0) (2024-06-12)
### Bug Fixes
- constrain entity fields type to string-keyed object ([#235](https://github.com/expo/entity/issues/235)) ([7e2cea1](https://github.com/expo/entity/commit/7e2cea16973a0ae1917f867cd25d6ef7c8eaecef))
# [0.35.0](https://github.com/expo/entity/compare/v0.34.0...v0.35.0) (2024-04-11)
### Bug Fixes
- Revert: require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([1bdfbd6](https://github.com/expo/entity/commit/1bdfbd6b562d1200e4029df0533b5adb2d917831))
# [0.34.0](https://github.com/expo/entity/compare/v0.33.0...v0.34.0) (2024-04-11)
### Features
- require explicit query context specification ([#219](https://github.com/expo/entity/issues/219)) ([8b0b31f](https://github.com/expo/entity/commit/8b0b31fdde5bd565aa527719003ef283a45f55cc))
# [0.33.0](https://github.com/expo/entity/compare/v0.32.0...v0.33.0) (2023-10-06)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.32.0](https://github.com/expo/entity/compare/v0.31.1...v0.32.0) (2023-06-10)
### Features
- add ability to specify knex transaction config ([#207](https://github.com/expo/entity/issues/207)) ([2069a0d](https://github.com/expo/entity/commit/2069a0d9fd1d8f7d090a71a90a758b2429ea5dd3))
- entity companion definition static thunk ([#210](https://github.com/expo/entity/issues/210)) ([4b18010](https://github.com/expo/entity/commit/4b18010d42be50ef329f428b08330e21bf676586))
# [0.31.0](https://github.com/expo/entity/compare/v0.30.0...v0.31.0) (2023-01-19)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.30.0](https://github.com/expo/entity/compare/v0.29.0...v0.30.0) (2022-12-23)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.29.0](https://github.com/expo/entity/compare/v0.28.0...v0.29.0) (2022-10-06)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.28.0](https://github.com/expo/entity/compare/v0.27.0...v0.28.0) (2022-09-13)
### Features
- add BigIntField ([#193](https://github.com/expo/entity/issues/193)) ([acbad82](https://github.com/expo/entity/commit/acbad826a37bb5a054b4dcf9be91d11aa5f7f29a))
- nested transactions ([#194](https://github.com/expo/entity/issues/194)) ([a77b914](https://github.com/expo/entity/commit/a77b914ad6dc018d1e309c9caf4971088a553ecf))
# [0.27.0](https://github.com/expo/entity/compare/v0.26.1...v0.27.0) (2022-08-09)
### Features
* add orderByRaw to loadManyByRawWhereClauseAsync ([#185](https://github.com/expo/entity/issues/185)) ([2817d78](https://github.com/expo/entity/commit/2817d78392dfecd0093111c24d581bdf01dba06d))
- add orderByRaw to loadManyByRawWhereClauseAsync ([#185](https://github.com/expo/entity/issues/185)) ([2817d78](https://github.com/expo/entity/commit/2817d78392dfecd0093111c24d581bdf01dba06d))
## [0.26.1](https://github.com/expo/entity/compare/v0.26.0...v0.26.1) (2022-07-28)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.26.0](https://github.com/expo/entity/compare/v0.25.3...v0.26.0) (2022-07-14)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.25.3](https://github.com/expo/entity/compare/v0.25.2...v0.25.3) (2022-03-12)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.25.2](https://github.com/expo/entity/compare/v0.25.1...v0.25.2) (2022-03-12)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.25.1](https://github.com/expo/entity/compare/v0.25.0...v0.25.1) (2022-03-10)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.25.0](https://github.com/expo/entity/compare/v0.24.0...v0.25.0) (2022-03-10)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.24.0](https://github.com/expo/entity/compare/v0.23.0...v0.24.0) (2022-02-16)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.23.0](https://github.com/expo/entity/compare/v0.22.0...v0.23.0) (2022-02-09)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.22.0](https://github.com/expo/entity/compare/v0.21.0...v0.22.0) (2022-02-04)
### Features
* Upgrade knex to 1.x in `@expo/entity-database-adapter-knex` ([#150](https://github.com/expo/entity/issues/150)) ([e1b0ee5](https://github.com/expo/entity/commit/e1b0ee528175646239d88aa71ffce73983eeb52f))
- Upgrade knex to 1.x in `@expo/entity-database-adapter-knex` ([#150](https://github.com/expo/entity/issues/150)) ([e1b0ee5](https://github.com/expo/entity/commit/e1b0ee528175646239d88aa71ffce73983eeb52f))
# [0.21.0](https://github.com/expo/entity/compare/v0.20.0...v0.21.0) (2022-01-03)
### Features
* add pre-commit callbacks on EntityQueryContext ([#147](https://github.com/expo/entity/issues/147)) ([f1d9847](https://github.com/expo/entity/commit/f1d9847210f5775de964509e44ce493e44482b21))
- add pre-commit callbacks on EntityQueryContext ([#147](https://github.com/expo/entity/issues/147)) ([f1d9847](https://github.com/expo/entity/commit/f1d9847210f5775de964509e44ce493e44482b21))
# [0.20.0](https://github.com/expo/entity/compare/v0.19.0...v0.20.0) (2021-12-29)
### Features
* add cascading deletion info ([#145](https://github.com/expo/entity/issues/145)) ([3727191](https://github.com/expo/entity/commit/372719176742d3ead6b7cbca29cb66b09b3fe09c))
- add cascading deletion info ([#145](https://github.com/expo/entity/issues/145)) ([3727191](https://github.com/expo/entity/commit/372719176742d3ead6b7cbca29cb66b09b3fe09c))
# [0.19.0](https://github.com/expo/entity/compare/v0.18.0...v0.19.0) (2021-10-28)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.18.0](https://github.com/expo/entity/compare/v0.17.0...v0.18.0) (2021-10-14)
### Features
* enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
* upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
- enable noImplicitOverride tsc setting ([#135](https://github.com/expo/entity/issues/135)) ([4263cb9](https://github.com/expo/entity/commit/4263cb9b8b69fe4fb68c74ec1c7aba04508a1555))
- upgrade TypeScript to 4.4 ([#134](https://github.com/expo/entity/issues/134)) ([7612392](https://github.com/expo/entity/commit/7612392dbfd9778d25c465c1626c372d75a6d05a))
# [0.17.0](https://github.com/expo/entity/compare/v0.16.0...v0.17.0) (2021-08-16)
### Features
* Add IntField and FloatField, deprecate NumberField ([#131](https://github.com/expo/entity/issues/131)) ([2f2d963](https://github.com/expo/entity/commit/2f2d963cf92c6575d78f8eeca344a4aadcc9b8d9))
- Add IntField and FloatField, deprecate NumberField ([#131](https://github.com/expo/entity/issues/131)) ([2f2d963](https://github.com/expo/entity/commit/2f2d963cf92c6575d78f8eeca344a4aadcc9b8d9))
# [0.16.0](https://github.com/expo/entity/compare/v0.15.0...v0.16.0) (2021-07-07)
### Features
* allow null field values in loadManyByFieldEqualityConjunctionAsync ([#130](https://github.com/expo/entity/issues/130)) ([2f37dc6](https://github.com/expo/entity/commit/2f37dc6a37d368b12b3c8375e04f0777fd448797))
- allow null field values in loadManyByFieldEqualityConjunctionAsync ([#130](https://github.com/expo/entity/issues/130)) ([2f37dc6](https://github.com/expo/entity/commit/2f37dc6a37d368b12b3c8375e04f0777fd448797))
# [0.15.0](https://github.com/expo/entity/compare/v0.14.1...v0.15.0) (2021-05-26)
### Bug Fixes
* upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
- upgrade ioredis, knex, and pg ([#125](https://github.com/expo/entity/issues/125)) ([7c43edf](https://github.com/expo/entity/commit/7c43edf3de37e8af13e2a99bd44f03f54803d9b9))
### Features
* secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
- secondary cache loader ([#123](https://github.com/expo/entity/issues/123)) ([4cba01e](https://github.com/expo/entity/commit/4cba01eb259c87d60b3026ce776e46f781363690))
## [0.14.1](https://github.com/expo/entity/compare/v0.14.0...v0.14.1) (2021-03-24)
### Bug Fixes
* typescript typedef of EntityMutationInfo ([#121](https://github.com/expo/entity/issues/121)) ([8e8f0dc](https://github.com/expo/entity/commit/8e8f0dc8636e343e29a6012d155e4111477a8989))
- typescript typedef of EntityMutationInfo ([#121](https://github.com/expo/entity/issues/121)) ([8e8f0dc](https://github.com/expo/entity/commit/8e8f0dc8636e343e29a6012d155e4111477a8989))
# [0.14.0](https://github.com/expo/entity/compare/v0.13.0...v0.14.0) (2021-03-24)
### Bug Fixes
* mutation validator and trigger cyclic import structure ([#118](https://github.com/expo/entity/issues/118)) ([b11dbc0](https://github.com/expo/entity/commit/b11dbc087ba5ad13c748032698dd73279b7960b0))
- mutation validator and trigger cyclic import structure ([#118](https://github.com/expo/entity/issues/118)) ([b11dbc0](https://github.com/expo/entity/commit/b11dbc087ba5ad13c748032698dd73279b7960b0))
# [0.13.0](https://github.com/expo/entity/compare/v0.12.0...v0.13.0) (2021-02-12)
### Features
* add basic field type runtime validators ([#113](https://github.com/expo/entity/issues/113)) ([6c4d4b0](https://github.com/expo/entity/commit/6c4d4b03b5404dd776a12c5eb6f0af77a8c964f8))
- add basic field type runtime validators ([#113](https://github.com/expo/entity/issues/113)) ([6c4d4b0](https://github.com/expo/entity/commit/6c4d4b03b5404dd776a12c5eb6f0af77a8c964f8))
# [0.12.0](https://github.com/expo/entity/compare/v0.11.0...v0.12.0) (2021-01-22)
### Bug Fixes
* move runInTransaction to ViewerContext ([#108](https://github.com/expo/entity/issues/108)) ([b7309e1](https://github.com/expo/entity/commit/b7309e18a2dd780b64cd306d566de1f12bf8df3a))
* remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
- move runInTransaction to ViewerContext ([#108](https://github.com/expo/entity/issues/108)) ([b7309e1](https://github.com/expo/entity/commit/b7309e18a2dd780b64cd306d566de1f12bf8df3a))
- remove cache and database adaptor flavor enums ([#109](https://github.com/expo/entity/issues/109)) ([72a77f8](https://github.com/expo/entity/commit/72a77f8b6893a4b76d3b1cce16659bdf3ce473ee))
# [0.11.0](https://github.com/expo/entity/compare/v0.10.0...v0.11.0) (2020-12-24)
### Features
* better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
* better database adapter error handling ([#101](https://github.com/expo/entity/issues/101)) ([5208aee](https://github.com/expo/entity/commit/5208aeedb0192f0460ba3d575770a04175f5c3aa))
- better cache adapter error handling ([#102](https://github.com/expo/entity/issues/102)) ([15546aa](https://github.com/expo/entity/commit/15546aad97734ab9ac67f1c012485f096a0e94f1))
- better database adapter error handling ([#101](https://github.com/expo/entity/issues/101)) ([5208aee](https://github.com/expo/entity/commit/5208aeedb0192f0460ba3d575770a04175f5c3aa))
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.9.0](https://github.com/expo/entity/compare/v0.8.1...v0.9.0) (2020-11-05)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.8.1](https://github.com/expo/entity/compare/v0.8.0...v0.8.1) (2020-10-09)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.8.0](https://github.com/expo/entity/compare/v0.7.1...v0.8.0) (2020-10-09)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.7.1](https://github.com/expo/entity/compare/v0.7.0...v0.7.1) (2020-10-01)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
# [0.7.0](https://github.com/expo/entity/compare/v0.6.0...v0.7.0) (2020-10-01)
### Bug Fixes
* fix index export for EntityMutationTrigger and EntityMutationValidator ([#73](https://github.com/expo/entity/issues/73)) ([7e829f3](https://github.com/expo/entity/commit/7e829f30fde0bd343cb8a584ccda5530cf61ec33))
* move cache invalidation to after transaction commit ([#77](https://github.com/expo/entity/issues/77)) ([dbc3c81](https://github.com/expo/entity/commit/dbc3c81b359a668b5134189ce6de49b5893bb5e7))
* upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
- fix index export for EntityMutationTrigger and EntityMutationValidator ([#73](https://github.com/expo/entity/issues/73)) ([7e829f3](https://github.com/expo/entity/commit/7e829f30fde0bd343cb8a584ccda5530cf61ec33))
- move cache invalidation to after transaction commit ([#77](https://github.com/expo/entity/issues/77)) ([dbc3c81](https://github.com/expo/entity/commit/dbc3c81b359a668b5134189ce6de49b5893bb5e7))
- upgrade packages ([#78](https://github.com/expo/entity/issues/78)) ([9891e74](https://github.com/expo/entity/commit/9891e7469467a28589f529c8d87b10fc2232d3ff))
# [0.6.0](https://github.com/expo/entity/compare/v0.5.2...v0.6.0) (2020-07-23)
### Bug Fixes
* separate out EntityMutationValidator ([#68](https://github.com/expo/entity/issues/68)) ([547a1ef](https://github.com/expo/entity/commit/547a1efcecd17cc085702d1a3e9888ce5b644b13))
- separate out EntityMutationValidator ([#68](https://github.com/expo/entity/issues/68)) ([547a1ef](https://github.com/expo/entity/commit/547a1efcecd17cc085702d1a3e9888ce5b644b13))
### Features
* entity mutation validators ([#67](https://github.com/expo/entity/issues/67)) ([fc4377d](https://github.com/expo/entity/commit/fc4377d8839da07417b88afc138f73556383d896))
* mutation triggers ([#65](https://github.com/expo/entity/issues/65)) ([fd6060c](https://github.com/expo/entity/commit/fd6060cc844f60635b3ce4c400c4877f2df8fa44))
- entity mutation validators ([#67](https://github.com/expo/entity/issues/67)) ([fc4377d](https://github.com/expo/entity/commit/fc4377d8839da07417b88afc138f73556383d896))
- mutation triggers ([#65](https://github.com/expo/entity/issues/65)) ([fd6060c](https://github.com/expo/entity/commit/fd6060cc844f60635b3ce4c400c4877f2df8fa44))
## [0.5.2](https://github.com/expo/entity/compare/v0.5.1...v0.5.2) (2020-07-02)
**Note:** Version bump only for package @expo/entity-database-adapter-knex
## [0.5.1](https://github.com/expo/entity/compare/v0.5.0...v0.5.1) (2020-07-01)
### Bug Fixes
* correct some typing issues and exports ([#57](https://github.com/expo/entity/issues/57)) ([e91cfba](https://github.com/expo/entity/commit/e91cfba99321c2c4da2078aafd8627cbf0ceca50))
- correct some typing issues and exports ([#57](https://github.com/expo/entity/issues/57)) ([e91cfba](https://github.com/expo/entity/commit/e91cfba99321c2c4da2078aafd8627cbf0ceca50))
# 0.5.0 (2020-07-01)
### Features
* support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
- support entity fields subset of db fields ([#49](https://github.com/expo/entity/issues/49)) ([4e40b2e](https://github.com/expo/entity/commit/4e40b2e521407e521d236978ec3b3b56db3990be))
@@ -39,4 +39,4 @@ export const createDefaultEntityCompanionProvider = (
}
);
};
```
```
Loaded 100 of 407 files, more files were not shown because too many files have changed in this diff. Show more