93 Commits

Author SHA1 Message Date
renovate-bot 8ae04b7fbd Update dependency typescript to v6
Lint / lint (push) Failing after 6s
Test build / test-build (pull_request) Failing after 8s
2026-03-31 20:51:36 +02:00
Julien Valverdé 9f4b3a1be5 Fix
Lint / lint (push) Successful in 15s
2026-03-31 20:44:25 +02:00
Julien Valverdé 6c31b39ddb Add Form.focusChunkAt
Lint / lint (push) Successful in 14s
2026-03-31 20:22:58 +02:00
Julien Valverdé 22672c98dc Add Form.focusTupleAt
Lint / lint (push) Successful in 15s
2026-03-31 18:47:51 +02:00
Julien Valverdé 1588bd0a1b Fix
Lint / lint (push) Successful in 15s
2026-03-31 15:21:06 +02:00
Julien Valverdé a373aee08b Fix Result
Lint / lint (push) Successful in 15s
2026-03-31 15:16:49 +02:00
Julien Valverdé 4d76b35c01 Fix
Lint / lint (push) Successful in 15s
2026-03-31 13:01:20 +02:00
Julien Valverdé 7572caea91 Fix
Lint / lint (push) Successful in 15s
2026-03-31 12:45:21 +02:00
Julien Valverdé 3d3491e8c9 Fix
Lint / lint (push) Failing after 14s
2026-03-31 02:31:00 +02:00
Julien Valverdé 717f264aa1 Refactor Result
Lint / lint (push) Failing after 41s
2026-03-31 02:26:32 +02:00
Julien Valverdé 9978f6811f Refactor Result
Lint / lint (push) Failing after 13s
2026-03-31 01:50:55 +02:00
Julien Valverdé 891488b3f5 Refactor Result
Lint / lint (push) Successful in 14s
2026-03-30 17:24:21 +02:00
Julien Valverdé 165b87e86c Fix
Lint / lint (push) Successful in 14s
2026-03-30 17:14:59 +02:00
Julien Valverdé b9ab700139 Fix
Lint / lint (push) Successful in 13s
2026-03-30 17:04:09 +02:00
Julien Valverdé 0de3256692 Apply dual API
Lint / lint (push) Successful in 14s
2026-03-30 17:00:38 +02:00
Julien Valverdé 8ec900b14a Fix
Lint / lint (push) Successful in 14s
2026-03-30 16:50:46 +02:00
Julien Valverdé de774a8517 Fix Form example
Lint / lint (push) Successful in 14s
2026-03-30 16:45:15 +02:00
Julien Valverdé 56f4e2d1f1 Fix
Lint / lint (push) Successful in 14s
2026-03-30 16:33:46 +02:00
Julien Valverdé ee1c0c45cd Fix
Lint / lint (push) Successful in 14s
2026-03-30 16:29:54 +02:00
Julien Valverdé 0f20587827 Refactor Form
Lint / lint (push) Successful in 14s
2026-03-30 16:25:16 +02:00
Julien Valverdé 075e92cb66 Refactor Form
Lint / lint (push) Successful in 13s
2026-03-30 14:20:25 +02:00
Julien Valverdé 212da47e12 Fix
Lint / lint (push) Successful in 42s
2026-03-30 11:09:43 +02:00
Julien Valverdé 0d1567f4a8 Refactor Form
Lint / lint (push) Successful in 14s
2026-03-30 01:00:13 +02:00
Julien Valverdé 7a16e17341 Refactor Form
Lint / lint (push) Successful in 44s
2026-03-29 10:59:40 +02:00
Julien Valverdé 4fdaca2973 Refactor Form
Lint / lint (push) Failing after 13s
2026-03-28 21:42:11 +01:00
Julien Valverdé c6655ba168 Refactor Form
Lint / lint (push) Failing after 13s
2026-03-28 21:36:43 +01:00
Julien Valverdé 8cc8aa4b56 Refactor Form
Lint / lint (push) Failing after 14s
2026-03-28 11:32:15 +01:00
Julien Valverdé 5f8965ce11 Refactor Form
Lint / lint (push) Failing after 15s
2026-03-28 10:37:11 +01:00
Julien Valverdé 1f36891e37 Refactor Form
Lint / lint (push) Failing after 42s
2026-03-28 10:18:49 +01:00
Julien Valverdé 7be29b7270 Upgrade dependencies
Lint / lint (push) Successful in 46s
2026-03-25 13:11:46 +01:00
Julien Valverdé 113d105fac Upgrade dependencies
Lint / lint (push) Successful in 15s
2026-03-25 12:45:15 +01:00
Julien Valverdé cbdcee039a Refactor Form to use Lens
Lint / lint (push) Successful in 14s
2026-03-25 12:41:26 +01:00
Julien Valverdé 47389bb842 Add Lens.useFromState
Lint / lint (push) Successful in 15s
2026-03-25 05:07:04 +01:00
Julien Valverdé 8ced53b6af Fix
Lint / lint (push) Successful in 14s
2026-03-25 04:56:40 +01:00
Julien Valverdé efdf58d8f9 Add Lens
Lint / lint (push) Successful in 14s
2026-03-25 04:54:52 +01:00
Julien Valverdé 1bf7e1dc4c Fix
Lint / lint (push) Successful in 14s
2026-03-24 23:26:50 +01:00
Julien Valverdé 571592b3dc Add fromSynchronizedRef
Lint / lint (push) Successful in 14s
2026-03-24 23:15:25 +01:00
Julien Valverdé 47073f2bf1 Add fromRef
Lint / lint (push) Failing after 44s
2026-03-24 23:05:22 +01:00
Julien Valverdé 13a12f5938 Update docs
Lint / lint (push) Successful in 14s
2026-03-24 12:32:36 +01:00
Julien Valverdé a2f3a07834 Update docs
Lint / lint (push) Successful in 13s
2026-03-24 12:28:48 +01:00
Julien Valverdé 8fb997a2a0 Update docs
Lint / lint (push) Successful in 14s
2026-03-24 12:19:22 +01:00
Julien Valverdé ff72c83ef0 Update docs
Lint / lint (push) Successful in 13s
2026-03-24 12:16:16 +01:00
Julien Valverdé 80c434d390 Add docs
Lint / lint (push) Successful in 13s
2026-03-24 12:09:08 +01:00
Julien Valverdé f1d0771356 Fix
Lint / lint (push) Successful in 13s
2026-03-24 11:51:38 +01:00
Julien Valverdé 2646e295d9 Add mapStream
Lint / lint (push) Successful in 13s
2026-03-24 11:48:01 +01:00
Julien Valverdé 45bf604381 Add tests
Lint / lint (push) Successful in 14s
2026-03-24 11:39:06 +01:00
Julien Valverdé 6fa34069ea Add Lens tests
Lint / lint (push) Successful in 14s
2026-03-24 11:22:25 +01:00
Julien Valverdé c338682bf2 Cleanup
Lint / lint (push) Successful in 13s
2026-03-24 11:15:54 +01:00
Julien Valverdé 087317171a Fix
Lint / lint (push) Successful in 13s
2026-03-24 11:10:59 +01:00
Julien Valverdé f08cc59fef Cleanup
Lint / lint (push) Successful in 14s
2026-03-24 10:20:54 +01:00
Julien Valverdé 34b9452c1c Fix
Lint / lint (push) Successful in 14s
2026-03-24 10:13:29 +01:00
Julien Valverdé 74dd87f4ea Fix
Lint / lint (push) Successful in 14s
2026-03-24 10:07:14 +01:00
Julien Valverdé 6e939884cc Fix
Lint / lint (push) Successful in 14s
2026-03-24 09:43:35 +01:00
Julien Valverdé 8c86c1ce76 Tests
Lint / lint (push) Failing after 13s
2026-03-24 09:28:05 +01:00
Julien Valverdé e175eac701 Add mapStructAt
Lint / lint (push) Failing after 15s
2026-03-24 09:17:31 +01:00
Julien Valverdé 7f18fc5553 Fix
Lint / lint (push) Failing after 13s
2026-03-24 09:00:29 +01:00
Julien Valverdé 3ff4e8758a Add utilities
Lint / lint (push) Successful in 14s
2026-03-24 08:52:08 +01:00
Julien Valverdé 4ae32fce49 Tests
Lint / lint (push) Successful in 14s
2026-03-24 07:24:52 +01:00
Julien Valverdé 1a25214984 Fix
Lint / lint (push) Successful in 14s
2026-03-24 07:24:01 +01:00
Julien Valverdé 580c6ec3d3 Fix
Lint / lint (push) Successful in 13s
2026-03-24 07:21:44 +01:00
Julien Valverdé c6db61c258 Fix
Lint / lint (push) Successful in 13s
2026-03-24 07:19:33 +01:00
Julien Valverdé eea6bcac4d Update
Lint / lint (push) Successful in 41s
2026-03-24 07:17:14 +01:00
Julien Valverdé ef1de00020 Add utilities
Lint / lint (push) Successful in 14s
2026-03-23 21:44:44 +01:00
Julien Valverdé 99f5e089f5 Tests
Lint / lint (push) Successful in 14s
2026-03-23 21:23:34 +01:00
Julien Valverdé 8430b4ddf6 Tests
Lint / lint (push) Successful in 13s
2026-03-23 21:19:45 +01:00
Julien Valverdé 88ad7cb1ac Tests
Lint / lint (push) Failing after 12s
2026-03-23 21:13:35 +01:00
Julien Valverdé 11d23aa10c Tests
Lint / lint (push) Failing after 12s
2026-03-23 21:00:40 +01:00
Julien Valverdé 3f05a5099e Fix
Lint / lint (push) Successful in 13s
2026-03-23 20:51:45 +01:00
Julien Valverdé a30c527803 Fix
Lint / lint (push) Successful in 14s
2026-03-23 20:31:26 +01:00
Julien Valverdé 10d69f977b Fix
Lint / lint (push) Successful in 14s
2026-03-23 20:21:55 +01:00
Julien Valverdé 7f8411e83c Fix
Lint / lint (push) Successful in 14s
2026-03-23 20:15:43 +01:00
Julien Valverdé e89babe223 Work
Lint / lint (push) Failing after 12s
2026-03-23 20:09:59 +01:00
Julien Valverdé aab613030d Fix
Lint / lint (push) Successful in 14s
2026-03-23 19:40:00 +01:00
Julien Valverdé 84bf50032b Work
Lint / lint (push) Failing after 13s
2026-03-23 14:40:05 +01:00
Julien Valverdé b8ad8a94c9 Fix
Lint / lint (push) Successful in 14s
2026-03-23 08:06:47 +01:00
Julien Valverdé 99bdd6a3ec Fix
Lint / lint (push) Successful in 14s
2026-03-23 07:57:21 +01:00
Julien Valverdé 64d6c20d06 Fix
Lint / lint (push) Successful in 15s
2026-03-23 04:27:15 +01:00
Julien Valverdé 285fc84275 Fix Lens
Lint / lint (push) Successful in 13s
2026-03-23 04:25:13 +01:00
Julien Valverdé 821ba95247 Fix
Lint / lint (push) Successful in 13s
2026-03-23 03:59:36 +01:00
Julien Valverdé 45c854a8d0 Fix make
Lint / lint (push) Successful in 14s
2026-03-23 03:03:35 +01:00
Julien Valverdé 54b05ed8da Work
Lint / lint (push) Successful in 14s
2026-03-23 02:33:19 +01:00
Julien Valverdé 0cb85adca0 ProxyRef -> Lens
Lint / lint (push) Successful in 14s
2026-03-23 01:39:40 +01:00
Julien Valverdé 7f57e034e4 Add ProxyRef
Lint / lint (push) Successful in 44s
2026-03-23 01:05:34 +01:00
Julien Valverdé 4f4dde988a Add Writable
Lint / lint (push) Successful in 15s
2026-03-23 00:43:40 +01:00
Julien Valverdé ba911ad598 Add Writable
Lint / lint (push) Successful in 13s
2026-03-22 12:19:54 +01:00
Julien Valverdé 672225d037 Fix tests
Lint / lint (push) Successful in 14s
2026-03-22 01:48:42 +01:00
Julien Valverdé 39125943ec Fix tests
Lint / lint (push) Successful in 13s
2026-03-22 01:46:30 +01:00
Julien Valverdé bba8d838b7 Add tests
Lint / lint (push) Failing after 43s
2026-03-22 01:41:30 +01:00
Julien Valverdé 7fcf3b825d Revert Vite versions
Lint / lint (push) Successful in 14s
2026-03-17 02:18:28 +01:00
Julien Valverdé 64c3ee8395 Regenerate lockfile
Lint / lint (push) Successful in 15s
2026-03-16 01:06:42 +01:00
renovate-bot 588ab5832f Update bun minor+patch updates (#35)
Lint / lint (push) Failing after 6s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@effect/language-service](https://github.com/Effect-TS/language-service) | [`^0.75.0` → `^0.80.0`](https://renovatebot.com/diffs/npm/@effect%2flanguage-service/0.75.1/0.80.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@effect%2flanguage-service/0.80.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@effect%2flanguage-service/0.75.1/0.80.0?slim=true) |
| [typescript](https://www.typescriptlang.org/) ([source](https://github.com/microsoft/TypeScript)) | [`~5.6.2` → `~5.9.0`](https://renovatebot.com/diffs/npm/typescript/5.6.3/5.9.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/typescript/5.9.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript/5.6.3/5.9.3?slim=true) |

---

### Release Notes

<details>
<summary>Effect-TS/language-service (@&#8203;effect/language-service)</summary>

### [`v0.80.0`](https://github.com/Effect-TS/language-service/releases/tag/%40effect/language-service%400.80.0)

[Compare Source](https://github.com/Effect-TS/language-service/compare/@effect/language-service@0.79.0...@effect/language-service@0.80.0)

##### Minor Changes

- [#&#8203;681](https://github.com/Effect-TS/language-service/pull/681) [`1017a54`](https://github.com/Effect-TS/language-service/commit/1017a5443b2e6919f18e57afb86373ba825037c9) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Generate a root `schema.json` for `tsconfig.json` plugin configuration, add typed Effect Language Service plugin options to that schema, and have `effect-language-service setup` add or remove the matching `$schema` entry automatically.

- [#&#8203;679](https://github.com/Effect-TS/language-service/pull/679) [`3664197`](https://github.com/Effect-TS/language-service/commit/3664197f271012d001f6074d40c5303826d632ce) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add inline `--lspconfig` support to the `effect-language-service diagnostics` CLI command so diagnostics runs can override the project plugin configuration without editing `tsconfig.json`.

### [`v0.79.0`](https://github.com/Effect-TS/language-service/releases/tag/%40effect/language-service%400.79.0)

[Compare Source](https://github.com/Effect-TS/language-service/compare/@effect/language-service@0.78.0...@effect/language-service@0.79.0)

##### Minor Changes

- [#&#8203;671](https://github.com/Effect-TS/language-service/pull/671) [`6b9c378`](https://github.com/Effect-TS/language-service/commit/6b9c378c4e1d0c83e4afe322cf44ccacd75d1cb4) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add the `extendsNativeError` diagnostic to warn when classes directly extend the native `Error` constructor, including common local aliases such as `const E = Error`.

  This helps steer users toward tagged errors that preserve stronger typing in the Effect failure channel.

- [#&#8203;678](https://github.com/Effect-TS/language-service/pull/678) [`0e9c11b`](https://github.com/Effect-TS/language-service/commit/0e9c11b4b3c076adef62e31722855ebc0071aaf6) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Generate the README diagnostics table from the diagnostic registry.

  Each diagnostic now declares:

  - whether it is fixable
  - which Effect versions it supports

  The generated table is checked in CI, and diagnostics tests verify that `fixable` matches the presence of non-suppression quick fixes.

- [#&#8203;676](https://github.com/Effect-TS/language-service/pull/676) [`2f982d6`](https://github.com/Effect-TS/language-service/commit/2f982d69541633aca2cd3bcdc89bdae7d17cb97b) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add the `nodeBuiltinImport` diagnostic to warn when importing Node.js built-in modules (`fs`, `path`, `child_process`) that have Effect-native counterparts in `@effect/platform`.

  This diagnostic covers ES module imports and top-level `require()` calls, matching both bare and `node:`-prefixed specifiers as well as subpath variants like `fs/promises`, `path/posix`, and `path/win32`. It defaults to severity `off` and provides no code fixes.

- [#&#8203;673](https://github.com/Effect-TS/language-service/pull/673) [`f9e24df`](https://github.com/Effect-TS/language-service/commit/f9e24df5db70110d5e84da45810bd82cf12fadc7) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add plugin options to better control patched `tsc` behavior.

  `ignoreEffectErrorsInTscExitCode` allows Effect diagnostics reported as errors to be ignored for exit-code purposes, and `skipDisabledOptimiziation` keeps disabled diagnostics eligible for comment-based overrides when patch mode is active.

- [#&#8203;674](https://github.com/Effect-TS/language-service/pull/674) [`54e8c16`](https://github.com/Effect-TS/language-service/commit/54e8c16865e99be9b6faec3e50c17d1e501242f9) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add the `serviceNotAsClass` diagnostic to warn when `ServiceMap.Service` is used as a variable assignment instead of in a class declaration.

  Includes an auto-fix that converts `const Config = ServiceMap.Service<Shape>("Config")` to `class Config extends ServiceMap.Service<Config, Shape>()("Config") {}`.

##### Patch Changes

- [#&#8203;675](https://github.com/Effect-TS/language-service/pull/675) [`d1f09c3`](https://github.com/Effect-TS/language-service/commit/d1f09c364bde5a14905b4a9d030830309b6aab43) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Rename the `skipDisabledOptimiziation` plugin option to `skipDisabledOptimization`.

  Example:

  ```json
  {
    "compilerOptions": {
      "plugins": [
        {
          "name": "@&#8203;effect/language-service",
          "skipDisabledOptimization": true
        }
      ]
    }
  }
  ```

### [`v0.78.0`](https://github.com/Effect-TS/language-service/releases/tag/%40effect/language-service%400.78.0)

[Compare Source](https://github.com/Effect-TS/language-service/compare/@effect/language-service@0.77.0...@effect/language-service@0.78.0)

##### Minor Changes

- [#&#8203;663](https://github.com/Effect-TS/language-service/pull/663) [`0e82d43`](https://github.com/Effect-TS/language-service/commit/0e82d437e91fe0b98c51b4b53c8d06f29aa41b8e) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Improve `effectFnOpportunity` inferred span naming for service-layer methods and align examples for Effect v4.

  The inferred span can now include service + method names (for example `MyService.log`) when the convertible function is a method inside a layer service object for strict supported patterns like:

  - `Layer.succeed(Service)(...)`
  - `Layer.sync(Service)(...)`
  - `Layer.effect(Service)(Effect.gen(...))`
  - `Layer.effect(Service, Effect.gen(...))`

  Also add Effect v4 diagnostics fixtures for:

  - `effectFnOpportunity_inferred.ts`
  - `effectFnOpportunity_inferredLayer.ts`

- [#&#8203;669](https://github.com/Effect-TS/language-service/pull/669) [`a010a29`](https://github.com/Effect-TS/language-service/commit/a010a29d219a22da2553d82da3bbabc3312106f5) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add a new `effectInFailure` diagnostic that warns when an `Effect` computation appears in the failure channel (`E`) of another `Effect`.

  The rule traverses Effect-typed expressions, unrolls union members of `E`, and reports when any member is itself a strict Effect type.

  It prefers innermost matches for nested cases (for example nested `Effect.try` in `catch`) to avoid noisy parent reports.

##### Patch Changes

- [#&#8203;666](https://github.com/Effect-TS/language-service/pull/666) [`06b3a6c`](https://github.com/Effect-TS/language-service/commit/06b3a6ce41c24459120c6a396804dadaf420786a) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Fix `effectFnOpportunity` inferred span naming for `Layer.*(this, ...)` patterns in class static members.

  When the inferred layer target is `this`, the diagnostic now uses the nearest enclosing class name (for example `MyService`) instead of the literal `this` token.

- [#&#8203;665](https://github.com/Effect-TS/language-service/pull/665) [`a95a679`](https://github.com/Effect-TS/language-service/commit/a95a6792e313ac920f6621858439b18d52c9c0d9) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Improve yield-based diagnostics and hover behavior by introducing `effectYieldableType` in `TypeParser` and using it in `missingReturnYieldStar`.

  - In Effect v4, yieldable values are recognized through `asEffect()` and mapped to Effect `A/E/R`.
  - In Effect v3, `effectYieldableType` falls back to standard `effectType` behavior.
  - `missingReturnYieldStar` now correctly handles yieldable values such as `Option.none()`.
  - Hover support for `yield*` was updated to use yieldable parsing paths.

- [#&#8203;664](https://github.com/Effect-TS/language-service/pull/664) [`934ef7e`](https://github.com/Effect-TS/language-service/commit/934ef7e0b58bc5260425b47a9efe1f4d0ccc26f0) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Improve `missingReturnYieldStar` safety by targeting only expression statements with top-level `yield*` expressions and validating the enclosing `Effect.gen` scope via `findEnclosingScopes`.

  This avoids edge cases where nested or wrapped `yield*` expressions could be matched incorrectly.

- [#&#8203;661](https://github.com/Effect-TS/language-service/pull/661) [`0f92686`](https://github.com/Effect-TS/language-service/commit/0f92686ac86b4f90eea436c542914ce59c39afb6) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Update effect dependency to v4.0.0-beta.19 and fix compatibility issues:

  - Fix `layerMagic` refactor producing `any` types in Layer channels by replacing `Array.partition` (which now uses the v4 `Filter.Filter` API) with a native loop for boolean partition logic
  - Add v4 Layer type detection shortcut using `"~effect/Layer"` TypeId property, matching the pattern already used for Effect type detection
  - Mark `Effect.filterMap` as unchanged in the outdated API migration database since it was re-added in v4

### [`v0.77.0`](https://github.com/Effect-TS/language-service/releases/tag/%40effect/language-service%400.77.0)

[Compare Source](https://github.com/Effect-TS/language-service/compare/@effect/language-service@0.76.0...@effect/language-service@0.77.0)

##### Minor Changes

- [#&#8203;655](https://github.com/Effect-TS/language-service/pull/655) [`c875de2`](https://github.com/Effect-TS/language-service/commit/c875de2c2334f740155f5a1e8a1a44636506d157) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add `outdatedApi` diagnostic that warns when using outdated Effect APIs in a project targeting a newer version of Effect.

##### Patch Changes

- [#&#8203;660](https://github.com/Effect-TS/language-service/pull/660) [`99a97a6`](https://github.com/Effect-TS/language-service/commit/99a97a6a4e275d03562de7ede2a2510f1c06f230) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Dispose TypeScript language services in tests to prevent resource leaks

  Added `languageService.dispose()` calls via `try/finally` patterns to all test files that create language services through `createServicesWithMockedVFS()`. This ensures proper cleanup of TypeScript compiler resources after each test completes, preventing memory leaks during test runs.

- [#&#8203;658](https://github.com/Effect-TS/language-service/pull/658) [`0154667`](https://github.com/Effect-TS/language-service/commit/0154667a23c95a8133751b3454b9233ddc39d8e3) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Fix outdated API diagnostic for Effect v4 compatibility

  - Fixed `TaggedError` completion to use `TaggedErrorClass` matching the v4 API
  - Removed `Schema.RequestClass` examples that no longer exist in v4
  - Updated Effect v4 harness to latest version

- [#&#8203;659](https://github.com/Effect-TS/language-service/pull/659) [`2699a80`](https://github.com/Effect-TS/language-service/commit/2699a80e3ecbce91db269cd34689752950d8d278) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add support for `Model.Class` from `effect/unstable/schema` in completions and diagnostics.

  The `classSelfMismatch` diagnostic now detects mismatched Self type parameters in `Model.Class` declarations, and the autocomplete for Self type in classes now suggests `Model.Class` when typing after `Model.`.

  ```ts
  import { Model } from "effect/unstable/schema";

  // autocomplete triggers after `Model.`
  export class MyDataModel extends Model.Class<MyDataModel>("MyDataModel")({
    id: Schema.String,
  }) {}
  ```

### [`v0.76.0`](https://github.com/Effect-TS/language-service/releases/tag/%40effect/language-service%400.76.0)

[Compare Source](https://github.com/Effect-TS/language-service/compare/@effect/language-service@0.75.1...@effect/language-service@0.76.0)

##### Minor Changes

- [#&#8203;651](https://github.com/Effect-TS/language-service/pull/651) [`aeab349`](https://github.com/Effect-TS/language-service/commit/aeab349b498c5bea4d050409a57f8f1900190c39) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Add refactor to convert `Effect.Service` to `Context.Tag` with a static `Layer` property.

  Supports all combinator kinds (`effect`, `scoped`, `sync`, `succeed`) and `dependencies`. The refactor replaces the `Effect.Service` class declaration with a `Context.Tag` class that has a `static layer` property using the corresponding `Layer` combinator.

  Before:

  ```ts
  export class MyService extends Effect.Service<MyService>()("MyService", {
    effect: Effect.gen(function* () {
      return { value: "hello" };
    }),
  }) {}
  ```

  After:

  ```ts
  export class MyService extends Context.Tag("MyService")<
    MyService,
    { value: string }
  >() {
    static layer = Layer.effect(
      this,
      Effect.gen(function* () {
        return { value: "hello" };
      })
    );
  }
  ```

- [#&#8203;654](https://github.com/Effect-TS/language-service/pull/654) [`2c93eab`](https://github.com/Effect-TS/language-service/commit/2c93eabfd7b799543832dc84304f20c90382c7eb) Thanks [@&#8203;mattiamanzati](https://github.com/mattiamanzati)! - Migrate internal Effect dependency from v3 to v4. This updates all CLI and core modules to use the Effect v4 API while maintaining full backward compatibility with existing functionality.

</details>

<details>
<summary>microsoft/TypeScript (typescript)</summary>

### [`v5.9.3`](https://github.com/microsoft/TypeScript/releases/tag/v5.9.3): TypeScript 5.9.3

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

Note: this tag was recreated to point at the correct commit. The npm package contained the correct content.

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/)

- [fixed issues query for Typescript 5.9.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.9.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+).
- *No specific changes for TypeScript 5.9.2 (Stable)*
- [fixed issues query for Typescript 5.9.3 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.9.3%22+is%3Aclosed+).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

### [`v5.9.2`](https://github.com/microsoft/TypeScript/releases/tag/v5.9.2): TypeScript 5.9

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

Note: this tag was recreated to point at the correct commit. The npm package contained the correct content.

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/)

- [fixed issues query for Typescript 5.9.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.9.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+).
- *No specific changes for TypeScript 5.9.2 (Stable)*

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

### [`v5.8.3`](https://github.com/microsoft/TypeScript/releases/tag/v5.8.3): TypeScript 5.8.3

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.8.2...v5.8.3)

Note: this tag was recreated to point at the correct commit. The npm package contained the correct content.

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-8/).

- [fixed issues query for Typescript 5.8.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.8.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.1%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.8.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.2%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.8.3 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.3%22+is%3Aclosed+).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

### [`v5.8.2`](https://github.com/microsoft/TypeScript/releases/tag/v5.8.2): TypeScript 5.8

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.7.3...v5.8.2)

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-8/).

- [fixed issues query for Typescript 5.8.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.8.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.1%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.8.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.8.2%22+is%3Aclosed+).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

### [`v5.7.3`](https://github.com/microsoft/TypeScript/releases/tag/v5.7.3): TypeScript 5.7.3

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.7.2...v5.7.3)

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-7/).

- [fixed issues query for Typescript 5.7.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.7.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.1%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.7.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.2%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.7.3 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.2%22+is%3Aclosed+).

Downloads are available on [npm](https://www.npmjs.com/package/typescript)

### [`v5.7.2`](https://github.com/microsoft/TypeScript/releases/tag/v5.7.2): TypeScript 5.7

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.6.3...v5.7.2)

For release notes, check out the [release announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-7/).

- [fixed issues query for Typescript 5.7.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.0%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.7.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.1%22+is%3Aclosed+).
- [fixed issues query for Typescript 5.7.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+5.7.2%22+is%3Aclosed+).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

</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.

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

---

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

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4zNi4yIiwidXBkYXRlZEluVmVyIjoiNDMuNzYuMiIsInRhcmdldEJyYW5jaCI6Im5leHQiLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #35
Co-authored-by: Renovate Bot <renovate-bot@valverde.cloud>
Co-committed-by: Renovate Bot <renovate-bot@valverde.cloud>
2026-03-16 01:05:35 +01:00
renovate-bot 94e838af43 Update dependency @vitejs/plugin-react to v6 (#36)
Lint / lint (push) Has been cancelled
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | [`^5.1.2` → `^6.0.0`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/5.2.0/6.0.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-react/6.0.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-react/5.2.0/6.0.1?slim=true) |

---

### Release Notes

<details>
<summary>vitejs/vite-plugin-react (@&#8203;vitejs/plugin-react)</summary>

### [`v6.0.1`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#601-2026-03-13)

[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/dcc901236079ef7fa99139f7ba7beebac583f301...1e94c06995c2afe2d1fee5aea2ef9720d35a7e02)

##### Expand `@rolldown/plugin-babel` peer dep range ([#&#8203;1146](https://github.com/vitejs/vite-plugin-react/pull/1146))

Expanded `@rolldown/plugin-babel` peer dep range to include `^0.2.0`.

### [`v6.0.0`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#600-2026-03-12)

[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/fda3a86095556b49ae3c995eb57a30d4e0b8fa8d...dcc901236079ef7fa99139f7ba7beebac583f301)

</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 has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni41IiwidXBkYXRlZEluVmVyIjoiNDMuNzYuMiIsInRhcmdldEJyYW5jaCI6Im5leHQiLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: #36
Co-authored-by: Renovate Bot <renovate-bot@valverde.cloud>
Co-committed-by: Renovate Bot <renovate-bot@valverde.cloud>
2026-03-16 01:05:01 +01:00
renovate-bot 154e27bcc0 Update dependency vite to v8 (#37)
Lint / lint (push) Has been cancelled
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [vite](https://vite.dev) ([source](https://github.com/vitejs/vite/tree/HEAD/packages/vite)) | [`^7.3.1` → `^8.0.0`](https://renovatebot.com/diffs/npm/vite/7.3.1/8.0.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vite/8.0.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/7.3.1/8.0.0?slim=true) |

---

### Release Notes

<details>
<summary>vitejs/vite (vite)</summary>

### [`v8.0.0`](https://github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#800-2026-03-12)

[Compare Source](https://github.com/vitejs/vite/compare/v7.3.1...v8.0.0)

![Vite 8 is here!](../../docs/public/og-image-announcing-vite8.webp)

Today, we're thrilled to announce the release of the next Vite major:

- **[Vite 8.0 announcement blog post](https://vite.dev/blog/announcing-vite8.html)**
- [Docs](https://vite.dev/) (translations: [简体中文](https://cn.vite.dev/), [日本語](https://ja.vite.dev/), [Español](https://es.vite.dev/), [Português](https://pt.vite.dev/), [한국어](https://ko.vite.dev/), [Deutsch](https://de.vite.dev/), [فارسی](https://fa.vite.dev/))
- [Migration Guide](https://vite.dev/guide/migration.html)

##### ⚠ BREAKING CHANGES

- remove `import.meta.hot.accept` resolution fallback ([#&#8203;21382](https://github.com/vitejs/vite/issues/21382))
- update default browser target ([#&#8203;21193](https://github.com/vitejs/vite/issues/21193))
- the epic `rolldown-vite` merge ([#&#8203;21189](https://github.com/vitejs/vite/issues/21189))

##### Features

- update rolldown to 1.0.0-rc.9 ([#&#8203;21813](https://github.com/vitejs/vite/issues/21813)) ([f05be0e](https://github.com/vitejs/vite/commit/f05be0eabf5c045b8892d463081da3c8fbf5a5ae))
- warn when `vite-tsconfig-paths` plugin is detected ([#&#8203;21781](https://github.com/vitejs/vite/issues/21781)) ([ada493e](https://github.com/vitejs/vite/commit/ada493e4214ef2028b96583550443a386be2e2ae))
- **css:** support es2025 build target for lightningcss ([#&#8203;21769](https://github.com/vitejs/vite/issues/21769)) ([08906e7](https://github.com/vitejs/vite/commit/08906e76f2fc0e55c8aea6243f6203ce0c78f106))
- forward browser console logs and errors to dev server terminal ([#&#8203;20916](https://github.com/vitejs/vite/issues/20916)) ([2540ed0](https://github.com/vitejs/vite/commit/2540ed06d0b6f93829d2d764b6a02f7dbfd14923))
- update rolldown to 1.0.0-rc.8 ([#&#8203;21790](https://github.com/vitejs/vite/issues/21790)) ([a0c950e](https://github.com/vitejs/vite/commit/a0c950e30945cc97fb2381a2affac086730fa31e))
- export `Visitor` and `ESTree` from `rolldown/utils` ([#&#8203;21664](https://github.com/vitejs/vite/issues/21664)) ([45de31e](https://github.com/vitejs/vite/commit/45de31e5ffcc514832aec96fa6e09a189c26d684))
- update rolldown to 1.0.0-rc.6 ([#&#8203;21714](https://github.com/vitejs/vite/issues/21714)) ([37a65f8](https://github.com/vitejs/vite/commit/37a65f8c31b5baeb4dadecfd4da98f81bae4202e))
- use util.inspect for CLI error display ([#&#8203;21668](https://github.com/vitejs/vite/issues/21668)) ([5f425a9](https://github.com/vitejs/vite/commit/5f425a9126ad1a483f482970bef8c29a0e721a3c))
- update rolldown to 1.0.0-rc.5 ([#&#8203;21660](https://github.com/vitejs/vite/issues/21660)) ([b3ddbc5](https://github.com/vitejs/vite/commit/b3ddbc54ee5b836852b09811c8e920b2b2cde7cb))
- update rolldown to 1.0.0-rc.4 ([#&#8203;21617](https://github.com/vitejs/vite/issues/21617)) ([1ee5c7f](https://github.com/vitejs/vite/commit/1ee5c7f796c24d7319fbd5258bbdce4968859efe))
- **wasm:** add SSR support for `.wasm?init` ([#&#8203;21102](https://github.com/vitejs/vite/issues/21102)) ([216a3b5](https://github.com/vitejs/vite/commit/216a3b53c610918027a7713a0d5495628f77d306))
- integrate devtools ([#&#8203;21331](https://github.com/vitejs/vite/issues/21331)) ([acbf507](https://github.com/vitejs/vite/commit/acbf507bcb05f9cd9525c765431b3e0ed97328e4))
- update rolldown to 1.0.0-rc.3 ([#&#8203;21554](https://github.com/vitejs/vite/issues/21554)) ([43358e9](https://github.com/vitejs/vite/commit/43358e97cd6485513f25ee11133333cba05841e3))
- **manifest:** add `assets` field for standalone CSS entry points ([#&#8203;21015](https://github.com/vitejs/vite/issues/21015)) ([f289b9b](https://github.com/vitejs/vite/commit/f289b9b0ce7821b1554b878d083c426e7a695b59))
- update rolldown to 1.0.0-rc.2 ([#&#8203;21512](https://github.com/vitejs/vite/issues/21512)) ([fa136a9](https://github.com/vitejs/vite/commit/fa136a9e68921f3ca396e0870193fe805fbfb7b4))
- **bundled-dev:** support worker in initial bundle ([#&#8203;21415](https://github.com/vitejs/vite/issues/21415)) ([f3d3149](https://github.com/vitejs/vite/commit/f3d31499c714fe5c5acf8355520624c662f9d79f))
- **dev:** detect port conflicts on wildcard hosts ([#&#8203;21381](https://github.com/vitejs/vite/issues/21381)) ([b0dd5a9](https://github.com/vitejs/vite/commit/b0dd5a993fd2f95c8cb2190a3ca4296bc9e06359))
- shortcuts case insensitive ([#&#8203;21224](https://github.com/vitejs/vite/issues/21224)) ([7796ade](https://github.com/vitejs/vite/commit/7796aded764bca987abfec8ab0ad0438c5a5e7eb))
- update rolldown to 1.0.0-rc.1 ([#&#8203;21463](https://github.com/vitejs/vite/issues/21463)) ([ff9dd7f](https://github.com/vitejs/vite/commit/ff9dd7fef0d3c898e317fca84a629828f3e28936))
- warn if `envPrefix` contains spaces ([#&#8203;21292](https://github.com/vitejs/vite/issues/21292)) ([9fcde3c](https://github.com/vitejs/vite/commit/9fcde3c870896a62fbca19be8ee14efab9393f4a))
- update rolldown to 1.0.0-beta.60 ([#&#8203;21408](https://github.com/vitejs/vite/issues/21408)) ([c33aa7c](https://github.com/vitejs/vite/commit/c33aa7cfd142a0dd38ed89589fc7b04cf8866791))
- update rolldown to 1.0.0-beta.59 ([#&#8203;21374](https://github.com/vitejs/vite/issues/21374)) ([0037943](https://github.com/vitejs/vite/commit/00379439fa62383460b056d587d0366597c19ab4))
- add `ignoreOutdatedRequests` option to `optimizeDeps` ([#&#8203;21364](https://github.com/vitejs/vite/issues/21364)) ([b2e75aa](https://github.com/vitejs/vite/commit/b2e75aabe93e3219f40fa5ad8755d53cdd2439b5))
- add ios to default esbuild targets ([#&#8203;21342](https://github.com/vitejs/vite/issues/21342)) ([daae6e9](https://github.com/vitejs/vite/commit/daae6e9f5dd223258a9e7a9a7fa22c8a4564902f))
- update rolldown to 1.0.0-beta.58 ([#&#8203;21354](https://github.com/vitejs/vite/issues/21354)) ([ba40cef](https://github.com/vitejs/vite/commit/ba40cef16d20590f7115d4d628d9b79fa0783473))
- update rolldown to 1.0.0-beta.57 ([#&#8203;21335](https://github.com/vitejs/vite/issues/21335)) ([d5412ef](https://github.com/vitejs/vite/commit/d5412ef4c472bc5fef4ed69cfee4ef4a929c6be9))
- **css:** support es2024 build target for lightningcss ([#&#8203;21294](https://github.com/vitejs/vite/issues/21294)) ([bd33b8e](https://github.com/vitejs/vite/commit/bd33b8e08768fdcef0b09e3eefa649fdcafdd397))
- update rolldown to 1.0.0-beta.56 ([#&#8203;21323](https://github.com/vitejs/vite/issues/21323)) ([9847a63](https://github.com/vitejs/vite/commit/9847a634cf36de2e6ac0043ffd22cefb1b5951bd))
- introduce v2 native plugins and enable it by default ([#&#8203;21268](https://github.com/vitejs/vite/issues/21268)) ([42f2ab3](https://github.com/vitejs/vite/commit/42f2ab3aec7cd0e03e195611b1e1ddabbedc9d61))
- **ssr:** avoid errors when rewriting already rewritten stacktrace ([#&#8203;21269](https://github.com/vitejs/vite/issues/21269)) ([98d9a33](https://github.com/vitejs/vite/commit/98d9a33274d9ac90780786afa612d916feddf2e3))
- update rolldown to 1.0.0-beta.55 ([#&#8203;21300](https://github.com/vitejs/vite/issues/21300)) ([2c8db85](https://github.com/vitejs/vite/commit/2c8db858d7081e898f63ce9569c3f19a91a10956))
- update rolldown to 1.0.0-beta.54 ([#&#8203;21267](https://github.com/vitejs/vite/issues/21267)) ([c751172](https://github.com/vitejs/vite/commit/c75117213cb1d2d13554fbc26a75e8df191c27eb))
- add a warning that is output when a plugin sets esbuild related options ([#&#8203;21218](https://github.com/vitejs/vite/issues/21218)) ([200646b](https://github.com/vitejs/vite/commit/200646b14397bfb80e9b29d2e4b33fcfc72d6b2c))
- highly experimental full bundle mode ([#&#8203;21235](https://github.com/vitejs/vite/issues/21235)) ([83d8c99](https://github.com/vitejs/vite/commit/83d8c99753d8bd5c1ea9b7a00e6998c865dad4e2))
- print esbuild options when both esbuild and oxc options are set ([#&#8203;21216](https://github.com/vitejs/vite/issues/21216)) ([08ae87b](https://github.com/vitejs/vite/commit/08ae87b14a3ce5f7cb3f1a382f497d36d0c2e01b))
- update default browser target ([#&#8203;21193](https://github.com/vitejs/vite/issues/21193)) ([8c3dd06](https://github.com/vitejs/vite/commit/8c3dd06bd9903bf0e6bc51f3554eea8cb6b26903))
- the epic `rolldown-vite` merge ([#&#8203;21189](https://github.com/vitejs/vite/issues/21189)) ([4a7f8d4](https://github.com/vitejs/vite/commit/4a7f8d43e6b14b89fef278c3ea86f9e3f64b7fc2))

##### Bug Fixes

- **deps:** update all non-major dependencies ([#&#8203;21786](https://github.com/vitejs/vite/issues/21786)) ([eaa4352](https://github.com/vitejs/vite/commit/eaa4352af8f8658e3a10a9945ad9c227fcb2f28a))
- use `watch.watcher` instead of `watch.notify` ([#&#8203;21793](https://github.com/vitejs/vite/issues/21793)) ([88953b3](https://github.com/vitejs/vite/commit/88953b331d6b6acf20dc1731745f27712b091fd9))
- **css:** apply `server.origin` to public file URLs in CSS (fix [#&#8203;18457](https://github.com/vitejs/vite/issues/18457)) ([#&#8203;21697](https://github.com/vitejs/vite/issues/21697)) ([c967f48](https://github.com/vitejs/vite/commit/c967f48b2e888585e977c3a8d829f7370eb40922))
- **deps:** update all non-major dependencies ([#&#8203;21732](https://github.com/vitejs/vite/issues/21732)) ([5c921ca](https://github.com/vitejs/vite/commit/5c921ca9bfe64327df82b04ae34ccfe0a7cfa297))
- **dev:** disable extglobs for consistency ([#&#8203;21745](https://github.com/vitejs/vite/issues/21745)) ([1958eeb](https://github.com/vitejs/vite/commit/1958eeb34f9eab93f106b785c1c2ddf97b5c3b3a))
- **lib:** keep annotation comments for es output ([#&#8203;21740](https://github.com/vitejs/vite/issues/21740)) ([dd3c4f4](https://github.com/vitejs/vite/commit/dd3c4f4cf0f9e665e56e6d9a9cb7007fc3ac0dc3))
- **optimizer:** avoid error happening with a package with asset entrypoint ([#&#8203;21766](https://github.com/vitejs/vite/issues/21766)) ([f7e1d07](https://github.com/vitejs/vite/commit/f7e1d0720e6b9f2ce5d358b6a23ebdb72c51be70))
- **ssr:** throw friendly error when calling `ssrLoadModule` with non-runnable ssr env ([#&#8203;21739](https://github.com/vitejs/vite/issues/21739)) ([1fa736e](https://github.com/vitejs/vite/commit/1fa736e802c3f0fa0eacdda1d5d1c976794459bd))
- **types:** remove extends ImportMeta from ModuleRunnerImportMeta ([#&#8203;21710](https://github.com/vitejs/vite/issues/21710)) ([0176d45](https://github.com/vitejs/vite/commit/0176d45deb29f5db1f551d20d828598c2130be36))
- **wasm:** reset assetUrlRE.lastIndex before .test() in SSR builds ([#&#8203;21780](https://github.com/vitejs/vite/issues/21780)) ([3a0d8d9](https://github.com/vitejs/vite/commit/3a0d8d94a8868f5e118c81bc35a657ef19ff7d82))
- **deps:** update all non-major dependencies ([#&#8203;21691](https://github.com/vitejs/vite/issues/21691)) ([521fdc0](https://github.com/vitejs/vite/commit/521fdc0ced51ddee7f728e6f891f36ebc6c0e1ce))
- **optimizer:** avoid duplicate modules when `preserveSymlinks` is enabled ([#&#8203;21720](https://github.com/vitejs/vite/issues/21720)) ([72165e0](https://github.com/vitejs/vite/commit/72165e0f58d49b894a366af25993cbffbd0ee986))
- **dev:** only treat EADDRINUSE as port conflict in wildcard pre-check ([#&#8203;21642](https://github.com/vitejs/vite/issues/21642)) ([e54e25f](https://github.com/vitejs/vite/commit/e54e25fbb9b721b2c655d17e35706e070c92ff70))
- **dev:** prevent concurrent server restarts ([#&#8203;21636](https://github.com/vitejs/vite/issues/21636)) ([8ce23a3](https://github.com/vitejs/vite/commit/8ce23a3b6e1eb86eef2b50c1bfbad072bbf9a03a))
- **dev:** return "502 Bad Gateway" on proxy failures instead of 500 ([#&#8203;21652](https://github.com/vitejs/vite/issues/21652)) ([e240df2](https://github.com/vitejs/vite/commit/e240df2ea4accd11631aac0f361e846a2e3140b0))
- clear tsconfig cache only when tsconfig.json is cached ([#&#8203;21622](https://github.com/vitejs/vite/issues/21622)) ([50c9675](https://github.com/vitejs/vite/commit/50c9675aa6c488b9887b7849a3397b7b29d1bd74))
- **deps:** update all non-major dependencies ([#&#8203;21594](https://github.com/vitejs/vite/issues/21594)) ([becdc5d](https://github.com/vitejs/vite/commit/becdc5dcc49efa3769c92e9929fb2280fd776206))
- **lib:** CSS injection point error with nested name IIFE output ([#&#8203;21606](https://github.com/vitejs/vite/issues/21606)) ([5003de6](https://github.com/vitejs/vite/commit/5003de6253ffdb23d1a52b1b5e06281d34f3a6ec))
- **module-runner:** incorrect column with `sourcemapInterceptor: "prepareStackTrace"` ([#&#8203;21562](https://github.com/vitejs/vite/issues/21562)) ([416c095](https://github.com/vitejs/vite/commit/416c0959ebd63db622c6579b53065e95f09c63f8))
- **module-runner:** prevent crash on negative column in stacktrace ([#&#8203;21585](https://github.com/vitejs/vite/issues/21585)) ([a075590](https://github.com/vitejs/vite/commit/a075590c4091240a6f0caca6b052500fd122f041))
- rolldownOptions/rollupOptions merging at environment level ([#&#8203;21612](https://github.com/vitejs/vite/issues/21612)) ([db2ecc7](https://github.com/vitejs/vite/commit/db2ecc7675c3932fc9e127b726ab8b0cab25f75c))
- **scanner:** respect tsconfig.json ([#&#8203;21547](https://github.com/vitejs/vite/issues/21547)) ([c6c04db](https://github.com/vitejs/vite/commit/c6c04db9c67d1b390d40fd1fd026d49204957f8d))
- avoid registering customization hook for import meta resolver multiple times ([#&#8203;21518](https://github.com/vitejs/vite/issues/21518)) ([8bb3203](https://github.com/vitejs/vite/commit/8bb32036792a6f522f5c947112f3d688add755a0))
- **config:** avoid watching rolldown runtime virtual module ([#&#8203;21545](https://github.com/vitejs/vite/issues/21545)) ([d18b139](https://github.com/vitejs/vite/commit/d18b13957b3bec08eae5a9ff80340488c8150d46))
- **deps:** update all non-major dependencies ([#&#8203;21540](https://github.com/vitejs/vite/issues/21540)) ([9ebaeaa](https://github.com/vitejs/vite/commit/9ebaeaac094db996b1d12665052633c20ac8a9cf))
- populate originalFileNames when resolving CSS asset paths ([#&#8203;21542](https://github.com/vitejs/vite/issues/21542)) ([8b47ff7](https://github.com/vitejs/vite/commit/8b47ff76d28630b4dc39c77fbd2762b4c36ad23d))
- **deps:** update all non-major dependencies ([#&#8203;21488](https://github.com/vitejs/vite/issues/21488)) ([2b32ca2](https://github.com/vitejs/vite/commit/2b32ca24fe9d742901c2cb5c88e6b1fd734f8c73))
- disable `tsconfig` option when loading config ([#&#8203;21517](https://github.com/vitejs/vite/issues/21517)) ([5025c35](https://github.com/vitejs/vite/commit/5025c358d119aa0b60d0505f9dd705950ad897f6))
- **optimizer:** map relative `new URL` paths to correct relative file location ([#&#8203;21434](https://github.com/vitejs/vite/issues/21434)) ([ca96cbc](https://github.com/vitejs/vite/commit/ca96cbc8eff23091c288f9eaf1944af2de3c564f))
- avoid using deprecated `output.inlineDynamicImport` option ([#&#8203;21464](https://github.com/vitejs/vite/issues/21464)) ([471ce62](https://github.com/vitejs/vite/commit/471ce6275663f068afa241a55711fd646d482385))
- use separate hook object for each environment ([#&#8203;21472](https://github.com/vitejs/vite/issues/21472)) ([66347f6](https://github.com/vitejs/vite/commit/66347f6df0e723d9d03ea31ab41ab5b767ad15ba))
- **deps:** update all non-major dependencies ([#&#8203;21440](https://github.com/vitejs/vite/issues/21440)) ([1835995](https://github.com/vitejs/vite/commit/18359959cb2960a2fb2b9a340e5ae27d122a1501))
- **dev:** avoid event emitter leak caused by `server.listen` callback ([#&#8203;21451](https://github.com/vitejs/vite/issues/21451)) ([602d786](https://github.com/vitejs/vite/commit/602d7865db2b12835c8225f3e87076bef4e247b9))
- lazy hook filter should work ([#&#8203;21443](https://github.com/vitejs/vite/issues/21443)) ([bc0c207](https://github.com/vitejs/vite/commit/bc0c207f537789d10d55caa4ee3697aa923b8426))
- **optimizer:** skip `rolldownCjsExternalPlugin` for `platform: neutral` ([#&#8203;21452](https://github.com/vitejs/vite/issues/21452)) ([d2fc4be](https://github.com/vitejs/vite/commit/d2fc4be0447e384e18e557b70f7c345d5bcea941))
- **deps:** update all non-major dependencies ([#&#8203;21389](https://github.com/vitejs/vite/issues/21389)) ([30f48df](https://github.com/vitejs/vite/commit/30f48df33ec9e9bd0b8164461eede5574398370b))
- **deps:** update esbuild peerDependency version ([#&#8203;21398](https://github.com/vitejs/vite/issues/21398)) ([4266c97](https://github.com/vitejs/vite/commit/4266c978083b3afa8d09ac3d3a110ee79f8efde2))
- **hmr:** trigger prune event when last import is removed ([#&#8203;20781](https://github.com/vitejs/vite/issues/20781)) ([#&#8203;21093](https://github.com/vitejs/vite/issues/21093)) ([7576735](https://github.com/vitejs/vite/commit/757673528c64945b77aee4a8e01669ccd0644973))
- **module-runner:** use `process.getBuiltinModule` instead of `import('node:module')` ([#&#8203;21402](https://github.com/vitejs/vite/issues/21402)) ([6633bcb](https://github.com/vitejs/vite/commit/6633bcb94149a2923cb6419aa481c5384bcf9310))
- support .env file mounts (FIFOs) ([#&#8203;21365](https://github.com/vitejs/vite/issues/21365)) ([6e6f82a](https://github.com/vitejs/vite/commit/6e6f82a067acc6e158be3b82edb3d7d2888f9af2))
- **css:** stylus Evaluator support ([#&#8203;21376](https://github.com/vitejs/vite/issues/21376)) ([cf9ace1](https://github.com/vitejs/vite/commit/cf9ace1b40b2767b9b9cbbabb084fe2e32afc535))
- **deps:** update all non-major dependencies ([#&#8203;21321](https://github.com/vitejs/vite/issues/21321)) ([9bc7c2e](https://github.com/vitejs/vite/commit/9bc7c2ed4f387fb982b84d1988a26af8990096f7))
- **import-analysis:** avoid cjs interop for built browser external module ([#&#8203;21333](https://github.com/vitejs/vite/issues/21333)) ([dc5a2fb](https://github.com/vitejs/vite/commit/dc5a2fb86f10c69b0ba6bc1831d9a29c79754ba2))
- **worker:** handle `new Worker(..., new URL(import.meta.url))` with trailing comma ([#&#8203;21325](https://github.com/vitejs/vite/issues/21325)) ([4a47241](https://github.com/vitejs/vite/commit/4a472418c02a0821900678778752c2d361bae3bd))
- detect `import.meta.resolve` when formatted across multiple lines ([#&#8203;21312](https://github.com/vitejs/vite/issues/21312)) ([130e718](https://github.com/vitejs/vite/commit/130e7181a55c524383c63bbfb1749d0ff7185cad))
- allow no-cors requests for non-script tag requests ([#&#8203;21299](https://github.com/vitejs/vite/issues/21299)) ([ef3d596](https://github.com/vitejs/vite/commit/ef3d59648fd9dd3f9b3118d09d216dc0afcb8c33))
- **deps:** update all non-major dependencies ([#&#8203;21285](https://github.com/vitejs/vite/issues/21285)) ([4635b2e](https://github.com/vitejs/vite/commit/4635b2e90f833d1048d76381e20208c0e0841e97))
- unreachable error when building with `experimental.bundledDev` is enabled ([#&#8203;21296](https://github.com/vitejs/vite/issues/21296)) ([e81c183](https://github.com/vitejs/vite/commit/e81c183f8c8ccaf7774ef0d0ee125bf63dbf30b4))
- **deps:** update all non-major dependencies ([#&#8203;21231](https://github.com/vitejs/vite/issues/21231)) ([859789c](https://github.com/vitejs/vite/commit/859789c856412dfa67969232ddda1df754febf40))
- don't strip base from imports ([#&#8203;21221](https://github.com/vitejs/vite/issues/21221)) ([7da742b](https://github.com/vitejs/vite/commit/7da742b478d2309c7d8de4cb55614a6476f350b4))
- allow exiting process before optimizer cleanup is done ([#&#8203;21170](https://github.com/vitejs/vite/issues/21170)) ([55ceffc](https://github.com/vitejs/vite/commit/55ceffc8976b8bb8c819f5b47419f8499ba3f843))
- plugin shortcut support ([#&#8203;21211](https://github.com/vitejs/vite/issues/21211)) ([6a3aca0](https://github.com/vitejs/vite/commit/6a3aca084356316811ff62cbedb5a410a249e789))

##### Performance Improvements

- **ssr:** skip circular import check for already-evaluated modules ([#&#8203;21632](https://github.com/vitejs/vite/issues/21632)) ([235140b](https://github.com/vitejs/vite/commit/235140b2d519e866fc28f88fe8155a5091630daf))
- use tsconfig cache for oxc transform in dev ([#&#8203;21643](https://github.com/vitejs/vite/issues/21643)) ([57ff177](https://github.com/vitejs/vite/commit/57ff177575bef6bee81a250e853d2c99affa0015))

##### Documentation

- bulk of typo fixes ([#&#8203;21507](https://github.com/vitejs/vite/issues/21507)) ([80755da](https://github.com/vitejs/vite/commit/80755dacab296cd2083fef29e09280ceb810a943))
- update `build.dynamicImportVarsOptions` ([#&#8203;21477](https://github.com/vitejs/vite/issues/21477)) ([54ce2ed](https://github.com/vitejs/vite/commit/54ce2ed15a95619bd18ac6609b7d7b5f42b4965d))
- clarify the pronunciation of `vite` in IPA symbols ([#&#8203;21238](https://github.com/vitejs/vite/issues/21238)) ([9b1d4d6](https://github.com/vitejs/vite/commit/9b1d4d6f348c8899bd7651bd802f583e99b901ee))
- ensure https links ([#&#8203;21266](https://github.com/vitejs/vite/issues/21266)) ([2eb259a](https://github.com/vitejs/vite/commit/2eb259a84859c7656718258afed08eb80670f530))

##### Miscellaneous Chores

- **deps-dev:** bump rollup from 4.57.1 to 4.59.0 ([#&#8203;21717](https://github.com/vitejs/vite/issues/21717)) ([25227bb](https://github.com/vitejs/vite/commit/25227bbdc7de0ed07cf7bdc9a1a733e3a9a132bc))
- **deps:** update dependency cac to v7 ([#&#8203;21788](https://github.com/vitejs/vite/issues/21788)) ([44e33ae](https://github.com/vitejs/vite/commit/44e33ae6a7b64130831f08b2a20d04cbd106898d))
- **deps:** update dependency rolldown-plugin-dts to ^0.22.2 ([#&#8203;21731](https://github.com/vitejs/vite/issues/21731)) ([d8ea652](https://github.com/vitejs/vite/commit/d8ea652a8b295d9e012ac7ea607d813c69f77791))
- **deps:** remove `fdir` and `@rollup/plugin-commonjs` ([#&#8203;21639](https://github.com/vitejs/vite/issues/21639)) ([5abffd5](https://github.com/vitejs/vite/commit/5abffd5d04bf586a60970588a14d7e3b79445093))
- **deps:** update dependency [@&#8203;rollup/plugin-alias](https://github.com/rollup/plugin-alias) to v6 ([#&#8203;21097](https://github.com/vitejs/vite/issues/21097)) ([44b5bdf](https://github.com/vitejs/vite/commit/44b5bdfcf2b2c1b73563ed0526c48584b756360f))
- fix broken link for future deprecations ([#&#8203;21603](https://github.com/vitejs/vite/issues/21603)) ([25f4501](https://github.com/vitejs/vite/commit/25f45013b94e50acc5c3e476691aa2210b33cae4))
- update `customResolver` deprecation message to mention `enforce: 'pre'` ([#&#8203;21576](https://github.com/vitejs/vite/issues/21576)) ([2ce34d5](https://github.com/vitejs/vite/commit/2ce34d5580ed118db6361696e6283c1fea74e685))
- update rolldown-plugin-dts to 0.22.1 ([#&#8203;21559](https://github.com/vitejs/vite/issues/21559)) ([77aab4b](https://github.com/vitejs/vite/commit/77aab4b7f1e3a2131477659c909a3fbe02faa0a0))
- **deps:** update dependency rolldown-plugin-dts to ^0.21.8 ([#&#8203;21539](https://github.com/vitejs/vite/issues/21539)) ([33881cb](https://github.com/vitejs/vite/commit/33881cb34f4587919713975d13ce255ef744472d))
- add missing versions to changelog ([#&#8203;21515](https://github.com/vitejs/vite/issues/21515)) ([4bfb239](https://github.com/vitejs/vite/commit/4bfb239686a17343bc46c0d7c968e28b0d64041f))
- **deps:** update rolldown-related dependencies ([#&#8203;21487](https://github.com/vitejs/vite/issues/21487)) ([5863e51](https://github.com/vitejs/vite/commit/5863e513fab6b481cfb42da86202f9db728c077d))
- **deps:** update rolldown-related dependencies ([#&#8203;21390](https://github.com/vitejs/vite/issues/21390)) ([be9dd4e](https://github.com/vitejs/vite/commit/be9dd4e08d899f9ed27f2bdcb81bf27d018377a6))
- fix typo in plugin.ts comment ([#&#8203;21435](https://github.com/vitejs/vite/issues/21435)) ([d31fc66](https://github.com/vitejs/vite/commit/d31fc6685b4dde33062bf4dfe46e0502de4e1449))
- replace caniuse link for ES2024 ([#&#8203;21355](https://github.com/vitejs/vite/issues/21355)) ([2ba4e99](https://github.com/vitejs/vite/commit/2ba4e990192845e01c733aa186c9599cdb5bb8fe))
- cleanup changelog ([#&#8203;21202](https://github.com/vitejs/vite/issues/21202)) ([8c8c56e](https://github.com/vitejs/vite/commit/8c8c56e1eb465e6dcd0c1b40f187228edc0e2be4))
- **deps:** update rolldown-related dependencies ([#&#8203;21230](https://github.com/vitejs/vite/issues/21230)) ([9349446](https://github.com/vitejs/vite/commit/9349446e9344bd81ccfb37af482f479cd1b59bbc))
- fix spelling error ([#&#8203;21223](https://github.com/vitejs/vite/issues/21223)) ([cc10e20](https://github.com/vitejs/vite/commit/cc10e207ae87ac122fc1efbb5ab01b516eb9cce8))

##### Code Refactoring

- don't add `optimization.inlineConst: { mode: 'smart' }` as it's enabled by default ([#&#8203;21794](https://github.com/vitejs/vite/issues/21794)) ([22b3d11](https://github.com/vitejs/vite/commit/22b3d111c38deb76d3c07010bf9903e3ee1befc1))
- enable some native plugins even with enable native plugin false ([#&#8203;21744](https://github.com/vitejs/vite/issues/21744)) ([fc46c79](https://github.com/vitejs/vite/commit/fc46c79797e9ec22a5a4fb1999f6268f72d586f8))
- avoid deprecated `legalComments` option ([#&#8203;21721](https://github.com/vitejs/vite/issues/21721)) ([e06496e](https://github.com/vitejs/vite/commit/e06496ef259015b5a89f33a9965be25f8bea0624))
- use `ESTree` types from `rolldown/utils` ([#&#8203;21719](https://github.com/vitejs/vite/issues/21719)) ([9239750](https://github.com/vitejs/vite/commit/9239750e619afba03243d6d583eaca55b510ddfe))
- deprecate `customResolver` in `resolve.alias` ([#&#8203;21476](https://github.com/vitejs/vite/issues/21476)) ([81275c9](https://github.com/vitejs/vite/commit/81275c907211ac766013e6232c2cdf559534bed1))
- remove unnecessary `@rolldown/pluginutils` ([#&#8203;21560](https://github.com/vitejs/vite/issues/21560)) ([c367b62](https://github.com/vitejs/vite/commit/c367b62693f19040e64d14915877f0b05b8ac7ae))
- enable some native plugins even with enable native plugin false ([#&#8203;21608](https://github.com/vitejs/vite/issues/21608)) ([5a4f692](https://github.com/vitejs/vite/commit/5a4f6924260ef0f2683177a99935160badea3f3b))
- use `rolldown/utils` ([#&#8203;21577](https://github.com/vitejs/vite/issues/21577)) ([e56103f](https://github.com/vitejs/vite/commit/e56103f180216306de738769303f31ad4c078b26))
- use internal devtools config ([#&#8203;21609](https://github.com/vitejs/vite/issues/21609)) ([9aea20f](https://github.com/vitejs/vite/commit/9aea20f4a190e0e1c7edc656361d636cd6ce642f))
- use parseEnv ([#&#8203;21586](https://github.com/vitejs/vite/issues/21586)) ([f859d2c](https://github.com/vitejs/vite/commit/f859d2cdfcc18f139775c208be068461a91602e5))
- **wasm:** remove native wasm helper plugin usage ([#&#8203;21566](https://github.com/vitejs/vite/issues/21566)) ([71a86be](https://github.com/vitejs/vite/commit/71a86be6d9b9ea0329e92f20671f4db1f020874d))
- enable some native plugins even with enable native plugin false ([#&#8203;21511](https://github.com/vitejs/vite/issues/21511)) ([b40292c](https://github.com/vitejs/vite/commit/b40292ce6a7dbbbbac9c6dae5f126b7f44c3e1b7))
- remove `experimental.enableNativePlugin: 'resolver'` ([#&#8203;21510](https://github.com/vitejs/vite/issues/21510)) ([f9d9213](https://github.com/vitejs/vite/commit/f9d92130fa79c638f77a3a8e6e55506f185d5a49))
- use `import.meta.dirname` everywhere ([#&#8203;21509](https://github.com/vitejs/vite/issues/21509)) ([7becf5f](https://github.com/vitejs/vite/commit/7becf5f8fe9041cff60f495ef975faaba68f9eb2))
- **optimizer:** simplify `rolldownCjsExternalPlugin` ([#&#8203;21450](https://github.com/vitejs/vite/issues/21450)) ([ebda8fd](https://github.com/vitejs/vite/commit/ebda8fd3c14f60e63d13d22102cb3d79a12f47a9))
- remove `import.meta.hot.accept` resolution fallback ([#&#8203;21382](https://github.com/vitejs/vite/issues/21382)) ([71d0797](https://github.com/vitejs/vite/commit/71d0797a719440f2a09b3364bfcf18576c2b67fb))
- **optimizer:** remove dead code ([#&#8203;21334](https://github.com/vitejs/vite/issues/21334)) ([e9a2cdb](https://github.com/vitejs/vite/commit/e9a2cdbb7d96a3f8e15d25774708d4f4ab626bb9))

##### Tests

- **ssr:** incorrect `handleInvoke` was called in server-worker-runner.invoke test ([#&#8203;21751](https://github.com/vitejs/vite/issues/21751)) ([b95ca22](https://github.com/vitejs/vite/commit/b95ca22460fe39fc862444f8c642fd6a0794c87c))
- add more type tests for `defineConfig` ([#&#8203;21698](https://github.com/vitejs/vite/issues/21698)) ([4fedbbd](https://github.com/vitejs/vite/commit/4fedbbdd9178a3f92e491233f44d49b3ac095c69))
- test case for catching invalid package resolution error ([#&#8203;21601](https://github.com/vitejs/vite/issues/21601)) ([c9b9359](https://github.com/vitejs/vite/commit/c9b9359fe88fc4b8a69a0d5c5a7eed8961fb6e57))
- **bundled-dev:** add worker test cases ([#&#8203;21557](https://github.com/vitejs/vite/issues/21557)) ([569bc98](https://github.com/vitejs/vite/commit/569bc98d6bc42fbd1835c1c24a493776030b6cb4))

##### Beta Changelogs

##### [8.0.0-beta.18](https://github.com/vitejs/vite/compare/v8.0.0-beta.17...v8.0.0-beta.18) (2026-03-09)

See [8.0.0-beta.18 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.18/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.17](https://github.com/vitejs/vite/compare/v8.0.0-beta.16...v8.0.0-beta.17) (2026-03-09)

See [8.0.0-beta.17 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.17/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.16](https://github.com/vitejs/vite/compare/v8.0.0-beta.15...v8.0.0-beta.16) (2026-02-27)

See [8.0.0-beta.16 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.16/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.15](https://github.com/vitejs/vite/compare/v8.0.0-beta.14...v8.0.0-beta.15) (2026-02-19)

See [8.0.0-beta.15 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.15/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.14](https://github.com/vitejs/vite/compare/v8.0.0-beta.13...v8.0.0-beta.14) (2026-02-12)

See [8.0.0-beta.14 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.14/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.13](https://github.com/vitejs/vite/compare/v8.0.0-beta.12...v8.0.0-beta.13) (2026-02-05)

See [8.0.0-beta.13 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.13/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.12](https://github.com/vitejs/vite/compare/v8.0.0-beta.11...v8.0.0-beta.12) (2026-02-03)

See [8.0.0-beta.12 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.12/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.11](https://github.com/vitejs/vite/compare/v8.0.0-beta.10...v8.0.0-beta.11) (2026-01-29)

See [8.0.0-beta.11 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.11/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.10](https://github.com/vitejs/vite/compare/v8.0.0-beta.9...v8.0.0-beta.10) (2026-01-24)

See [8.0.0-beta.10 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.10/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.9](https://github.com/vitejs/vite/compare/v8.0.0-beta.8...v8.0.0-beta.9) (2026-01-22)

See [8.0.0-beta.9 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.9/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.8](https://github.com/vitejs/vite/compare/v8.0.0-beta.7...v8.0.0-beta.8) (2026-01-15)

See [8.0.0-beta.8 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.8/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.7](https://github.com/vitejs/vite/compare/v8.0.0-beta.6...v8.0.0-beta.7) (2026-01-08)

See [8.0.0-beta.7 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.7/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.6](https://github.com/vitejs/vite/compare/v8.0.0-beta.5...v8.0.0-beta.6) (2026-01-07)

See [8.0.0-beta.6 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.6/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.5](https://github.com/vitejs/vite/compare/v8.0.0-beta.4...v8.0.0-beta.5) (2025-12-25)

See [8.0.0-beta.5 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.5/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.4](https://github.com/vitejs/vite/compare/v8.0.0-beta.3...v8.0.0-beta.4) (2025-12-22)

See [8.0.0-beta.4 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.4/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.3](https://github.com/vitejs/vite/compare/v8.0.0-beta.2...v8.0.0-beta.3) (2025-12-18)

See [8.0.0-beta.3 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.3/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.2](https://github.com/vitejs/vite/compare/v8.0.0-beta.1...v8.0.0-beta.2) (2025-12-12)

See [8.0.0-beta.2 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.2/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.1](https://github.com/vitejs/vite/compare/v8.0.0-beta.0...v8.0.0-beta.1) (2025-12-08)

See [8.0.0-beta.1 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.1/packages/vite/CHANGELOG.md)

##### [8.0.0-beta.0](https://github.com/vitejs/vite/compare/v7.2.4...v8.0.0-beta.0) (2025-12-03)

See [8.0.0-beta.0 changelog](https://github.com/vitejs/vite/blob/v8.0.0-beta.0/packages/vite/CHANGELOG.md)

##### Rolldown-Vite changelogs

See [rolldown-vite changelog](https://github.com/vitejs/rolldown-vite/blob/v7.2.10/packages/vite/CHANGELOG.md)

</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 has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni41IiwidXBkYXRlZEluVmVyIjoiNDMuNzYuMiIsInRhcmdldEJyYW5jaCI6Im5leHQiLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://git.valverde.cloud/Thilawyn/effect-fc/pulls/37
Co-authored-by: Renovate Bot <renovate-bot@valverde.cloud>
Co-committed-by: Renovate Bot <renovate-bot@valverde.cloud>
2026-03-16 01:04:45 +01:00
20 changed files with 991 additions and 727 deletions
+231 -360
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@effect-fc/monorepo", "name": "@effect-fc/monorepo",
"packageManager": "bun@1.3.6", "packageManager": "bun@1.3.11",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"./packages/*" "./packages/*"
@@ -15,12 +15,12 @@
"clean:modules": "turbo clean:modules && rm -rf node_modules" "clean:modules": "turbo clean:modules && rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.11", "@biomejs/biome": "^2.4.9",
"@effect/language-service": "^0.75.0", "@effect/language-service": "^0.84.2",
"@types/bun": "^1.3.6", "@types/bun": "^1.3.11",
"npm-check-updates": "^19.3.1", "npm-check-updates": "^19.6.6",
"npm-sort": "^0.0.4", "npm-sort": "^0.0.4",
"turbo": "^2.7.5", "turbo": "^2.8.21",
"typescript": "^5.9.3" "typescript": "^6.0.2"
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@
"@docusaurus/module-type-aliases": "3.9.2", "@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2", "@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2", "@docusaurus/types": "3.9.2",
"typescript": "~5.6.2" "typescript": "~6.0.0"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
+5 -2
View File
@@ -38,11 +38,14 @@
"clean:modules": "rm -rf node_modules" "clean:modules": "rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@effect/platform-browser": "^0.74.0" "@effect/platform-browser": "^0.76.0"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0", "@types/react": "^19.2.0",
"effect": "^3.19.0", "effect": "^3.21.0",
"react": "^19.2.0" "react": "^19.2.0"
},
"dependencies": {
"effect-lens": "^0.1.4"
} }
} }
+258 -183
View File
@@ -1,105 +1,101 @@
import { Array, Cause, Chunk, type Context, type Duration, Effect, Equal, Exit, Fiber, flow, Hash, HashMap, identity, Option, ParseResult, Pipeable, Predicate, Ref, Schema, type Scope, Stream } from "effect" import { Array, Cause, Chunk, type Context, type Duration, Effect, Equal, Exit, Fiber, Function, identity, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream } from "effect"
import type * as React from "react" import type * as React from "react"
import * as Component from "./Component.js" import * as Component from "./Component.js"
import * as Lens from "./Lens.js"
import * as Mutation from "./Mutation.js" import * as Mutation from "./Mutation.js"
import * as PropertyPath from "./PropertyPath.js"
import * as Result from "./Result.js" import * as Result from "./Result.js"
import * as Subscribable from "./Subscribable.js" import * as Subscribable from "./Subscribable.js"
import * as SubscriptionRef from "./SubscriptionRef.js" import * as SubscriptionRef from "./SubscriptionRef.js"
import * as SubscriptionSubRef from "./SubscriptionSubRef.js"
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form") export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
export type FormTypeId = typeof FormTypeId export type FormTypeId = typeof FormTypeId
export interface Form<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never> export interface Form<out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never>
extends Pipeable.Pipeable { extends Pipeable.Pipeable {
readonly [FormTypeId]: FormTypeId readonly [FormTypeId]: FormTypeId
readonly path: P
readonly value: Subscribable.Subscribable<Option.Option<A>, ER, never>
readonly encodedValue: Lens.Lens<I, ER, EW, never, never>
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>
readonly isValidating: Subscribable.Subscribable<boolean, ER, never>
readonly canSubmit: Subscribable.Subscribable<boolean, never, never>
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>
}
export class FormImpl<out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never>
extends Pipeable.Class() implements Form<P, A, I, ER, EW> {
readonly [FormTypeId]: FormTypeId = FormTypeId
constructor(
readonly path: P,
readonly value: Subscribable.Subscribable<Option.Option<A>, ER, never>,
readonly encodedValue: Lens.Lens<I, ER, EW, never, never>,
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>,
readonly canSubmit: Subscribable.Subscribable<boolean, never, never>,
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>,
) {
super()
}
}
export const RootFormTypeId: unique symbol = Symbol.for("@effect-fc/Form/RootForm")
export type RootFormTypeId = typeof RootFormTypeId
export interface RootForm<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never>
extends Form<readonly [], A, I, never, never> {
readonly schema: Schema.Schema<A, I, R> readonly schema: Schema.Schema<A, I, R>
readonly context: Context.Context<Scope.Scope | R> readonly context: Context.Context<Scope.Scope | R>
readonly mutation: Mutation.Mutation< readonly mutation: Mutation.Mutation<
readonly [value: A, form: Form<A, I, R, unknown, unknown, unknown>], readonly [value: A, form: RootForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP MA, ME, MR, MP
> >
readonly autosubmit: boolean readonly autosubmit: boolean
readonly debounce: Option.Option<Duration.DurationInput>
readonly value: Subscribable.Subscribable<Option.Option<A>> readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never>
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
readonly error: Subscribable.Subscribable<Option.Option<ParseResult.ParseError>>
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>
readonly canSubmit: Subscribable.Subscribable<boolean>
field<const P extends PropertyPath.Paths<I>>(
path: P
): Effect.Effect<FormField<PropertyPath.ValueFromPath<A, P>, PropertyPath.ValueFromPath<I, P>>>
readonly run: Effect.Effect<void> readonly run: Effect.Effect<void>
readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException>
} }
export class FormImpl<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never> export class RootFormImpl<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never>
extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> { extends Pipeable.Class() implements RootForm<A, I, R, MA, ME, MR, MP> {
readonly [FormTypeId]: FormTypeId = FormTypeId readonly [FormTypeId]: FormTypeId = FormTypeId
readonly [RootFormTypeId]: RootFormTypeId = RootFormTypeId
readonly path = [] as const
constructor( constructor(
readonly schema: Schema.Schema<A, I, R>, readonly schema: Schema.Schema<A, I, R>,
readonly context: Context.Context<Scope.Scope | R>, readonly context: Context.Context<Scope.Scope | R>,
readonly mutation: Mutation.Mutation< readonly mutation: Mutation.Mutation<
readonly [value: A, form: Form<A, I, R, unknown, unknown, unknown>], readonly [value: A, form: RootForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP MA, ME, MR, MP
>, >,
readonly autosubmit: boolean, readonly autosubmit: boolean,
readonly debounce: Option.Option<Duration.DurationInput>,
readonly value: SubscriptionRef.SubscriptionRef<Option.Option<A>>, readonly value: Lens.Lens<Option.Option<A>, never, never, never, never>,
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>, readonly encodedValue: Lens.Lens<I, never, never, never, never>,
readonly error: SubscriptionRef.SubscriptionRef<Option.Option<ParseResult.ParseError>>, readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>,
readonly validationFiber: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>>, readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never, never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>,
readonly canSubmit: Subscribable.Subscribable<boolean, never, never>,
readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>,
readonly runSemaphore: Effect.Semaphore, readonly runSemaphore: Effect.Semaphore,
readonly fieldCache: Ref.Ref<HashMap.HashMap<FormFieldKey, FormField<unknown, unknown>>>,
) { ) {
super() super()
this.canSubmit = Subscribable.map(
Subscribable.zipLatestAll(this.value, this.error, this.validationFiber, this.mutation.result),
([value, error, validationFiber, result]) => (
Option.isSome(value) &&
Option.isNone(error) &&
Option.isNone(validationFiber) &&
!(Result.isRunning(result) || Result.hasRefreshingFlag(result))
),
)
} }
field<const P extends PropertyPath.Paths<I>>(
path: P
): Effect.Effect<FormField<PropertyPath.ValueFromPath<A, P>, PropertyPath.ValueFromPath<I, P>>> {
const key = new FormFieldKey(path)
return this.fieldCache.pipe(
Effect.map(HashMap.get(key)),
Effect.flatMap(Option.match({
onSome: v => Effect.succeed(v as FormField<PropertyPath.ValueFromPath<A, P>, PropertyPath.ValueFromPath<I, P>>),
onNone: () => Effect.tap(
Effect.succeed(makeFormField(this as Form<A, I, R, MA, ME, MR, MP>, path)),
v => Ref.update(this.fieldCache, HashMap.set(key, v as FormField<unknown, unknown>)),
),
})),
)
}
readonly canSubmit: Subscribable.Subscribable<boolean>
get run(): Effect.Effect<void> { get run(): Effect.Effect<void> {
return this.runSemaphore.withPermits(1)(Stream.runForEach( return this.runSemaphore.withPermits(1)(Stream.runForEach(
this.encodedValue.changes.pipe( this.encodedValue.changes,
Option.isSome(this.debounce) ? Stream.debounce(this.debounce.value) : identity
),
encodedValue => this.validationFiber.pipe( encodedValue => Lens.get(this.validationFiber).pipe(
Effect.andThen(Option.match({ Effect.andThen(Option.match({
onSome: Fiber.interrupt, onSome: Fiber.interrupt,
onNone: () => Effect.void, onNone: () => Effect.void,
@@ -110,18 +106,21 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
exit => Effect.andThen( exit => Effect.andThen(
Exit.matchEffect(exit, { Exit.matchEffect(exit, {
onSuccess: v => Effect.andThen( onSuccess: v => Effect.andThen(
Ref.set(this.value, Option.some(v)), Lens.set(this.value, Option.some(v)),
Ref.set(this.error, Option.none()), Lens.set(this.issues, Array.empty()),
), ),
onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), { onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), {
onSome: e => Ref.set(this.error, Option.some(e)), onSome: e => Effect.flatMap(
ParseResult.ArrayFormatter.formatError(e),
v => Lens.set(this.issues, v),
),
onNone: () => Effect.void, onNone: () => Effect.void,
}), }),
}), }),
Ref.set(this.validationFiber, Option.none()), Lens.set(this.validationFiber, Option.none()),
), ),
)).pipe( )).pipe(
Effect.tap(fiber => Ref.set(this.validationFiber, Option.some(fiber))), Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
Effect.andThen(Fiber.join), Effect.andThen(Fiber.join),
Effect.andThen(value => this.autosubmit Effect.andThen(value => this.autosubmit
? Effect.asVoid(Effect.forkScoped(this.submitValue(value))) ? Effect.asVoid(Effect.forkScoped(this.submitValue(value)))
@@ -136,7 +135,7 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
} }
get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> { get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> {
return this.value.pipe( return Lens.get(this.value).pipe(
Effect.andThen(identity), Effect.andThen(identity),
Effect.andThen(value => this.submitValue(value)), Effect.andThen(value => this.submitValue(value)),
) )
@@ -153,7 +152,10 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
e => e._tag === "ParseError", e => e._tag === "ParseError",
), ),
{ {
onSome: e => Ref.set(this.error, Option.some(e)), onSome: e => Effect.flatMap(
ParseResult.ArrayFormatter.formatError(e),
v => Lens.set(this.issues, v),
),
onNone: () => Effect.void, onNone: () => Effect.void,
}, },
) )
@@ -164,42 +166,59 @@ extends Pipeable.Class() implements Form<A, I, R, MA, ME, MR, MP> {
} }
} }
export const isForm = (u: unknown): u is Form<unknown, unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, FormTypeId)
export const isForm = (u: unknown): u is Form<readonly PropertyKey[], unknown, unknown> => Predicate.hasProperty(u, FormTypeId)
export const isRootForm = (u: unknown): u is RootForm<readonly PropertyKey[], unknown, unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, RootFormTypeId)
export declare namespace make { export declare namespace make {
export interface Options<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never> export interface Options<in out A, in out I = A, in out R = never, in out MA = void, in out ME = never, in out MR = never, in out MP = never>
extends Mutation.make.Options< extends Mutation.make.Options<
readonly [value: NoInfer<A>, form: Form<NoInfer<A>, NoInfer<I>, NoInfer<R>, unknown, unknown, unknown>], readonly [value: NoInfer<A>, form: RootForm<NoInfer<A>, NoInfer<I>, NoInfer<R>, unknown, unknown, unknown>],
MA, ME, MR, MP MA, ME, MR, MP
> { > {
readonly schema: Schema.Schema<A, I, R> readonly schema: Schema.Schema<A, I, R>
readonly initialEncodedValue: NoInfer<I> readonly initialEncodedValue: NoInfer<I>
readonly autosubmit?: boolean readonly autosubmit?: boolean
readonly debounce?: Duration.DurationInput
} }
} }
export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void, ME = never, MR = never, MP = never>( export const make = Effect.fnUntraced(function* <A, I = A, R = never, MA = void, ME = never, MR = never, MP = never>(
options: make.Options<A, I, R, MA, ME, MR, MP> options: make.Options<A, I, R, MA, ME, MR, MP>
): Effect.fn.Return< ): Effect.fn.Return<
Form<A, I, R, MA, ME, Result.forkEffect.OutputContext<MA, ME, MR, MP>, MP>, RootForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never, never,
Scope.Scope | R | Result.forkEffect.OutputContext<MA, ME, MR, MP> Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> { > {
return new FormImpl( const mutation = yield* Mutation.make(options)
const valueLens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<A>()))
const issuesLens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make<readonly ParseResult.ArrayFormatterIssue[]>(Array.empty()))
const validationFiberLens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>()))
return new RootFormImpl(
options.schema, options.schema,
yield* Effect.context<Scope.Scope | R>(), yield* Effect.context<Scope.Scope | R>(),
yield* Mutation.make(options), mutation,
options.autosubmit ?? false, options.autosubmit ?? false,
Option.fromNullable(options.debounce),
yield* SubscriptionRef.make(Option.none<A>()), valueLens,
yield* SubscriptionRef.make(options.initialEncodedValue), Lens.fromSubscriptionRef(yield* SubscriptionRef.make(options.initialEncodedValue)),
yield* SubscriptionRef.make(Option.none<ParseResult.ParseError>()), issuesLens,
yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>()), validationFiberLens,
Subscribable.map(validationFiberLens, Option.isSome),
Subscribable.map(
Subscribable.zipLatestAll(valueLens, issuesLens, validationFiberLens, mutation.result),
([value, issues, validationFiber, result]) => (
Option.isSome(value) &&
Array.isEmptyReadonlyArray(issues) &&
Option.isNone(validationFiber) &&
!(Result.isRunning(result) || Result.hasRefreshingFlag(result))
),
),
Subscribable.map(mutation.result, result => Result.isRunning(result) || Result.hasRefreshingFlag(result)),
yield* Effect.makeSemaphore(1), yield* Effect.makeSemaphore(1),
yield* Ref.make(HashMap.empty<FormFieldKey, FormField<unknown, unknown>>()),
) )
}) })
@@ -211,83 +230,125 @@ export declare namespace service {
export const service = <A, I = A, R = never, MA = void, ME = never, MR = never, MP = never>( export const service = <A, I = A, R = never, MA = void, ME = never, MR = never, MP = never>(
options: service.Options<A, I, R, MA, ME, MR, MP> options: service.Options<A, I, R, MA, ME, MR, MP>
): Effect.Effect< ): Effect.Effect<
Form<A, I, R, MA, ME, Result.forkEffect.OutputContext<MA, ME, MR, MP>, MP>, RootForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never, never,
Scope.Scope | R | Result.forkEffect.OutputContext<MA, ME, MR, MP> Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> => Effect.tap( > => Effect.tap(
make(options), make(options),
form => Effect.forkScoped(form.run), form => Effect.forkScoped(form.run),
) )
export const FormFieldTypeId: unique symbol = Symbol.for("@effect-fc/Form/FormField") const filterIssuesByPath = (
export type FormFieldTypeId = typeof FormFieldTypeId issues: readonly ParseResult.ArrayFormatterIssue[],
path: readonly PropertyKey[],
export interface FormField<in out A, in out I = A> ): readonly ParseResult.ArrayFormatterIssue[] => Array.filter(issues, issue =>
extends Pipeable.Pipeable { issue.path.length >= path.length && Array.every(path, (p, i) => p === issue.path[i])
readonly [FormFieldTypeId]: FormFieldTypeId
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>
readonly isValidating: Subscribable.Subscribable<boolean>
readonly isSubmitting: Subscribable.Subscribable<boolean>
}
class FormFieldImpl<in out A, in out I = A>
extends Pipeable.Class() implements FormField<A, I> {
readonly [FormFieldTypeId]: FormFieldTypeId = FormFieldTypeId
constructor(
readonly value: Subscribable.Subscribable<Option.Option<A>, Cause.NoSuchElementException>,
readonly encodedValue: SubscriptionRef.SubscriptionRef<I>,
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[]>,
readonly isValidating: Subscribable.Subscribable<boolean>,
readonly isSubmitting: Subscribable.Subscribable<boolean>,
) {
super()
}
}
const FormFieldKeyTypeId: unique symbol = Symbol.for("@effect-fc/Form/FormFieldKey")
type FormFieldKeyTypeId = typeof FormFieldKeyTypeId
class FormFieldKey implements Equal.Equal {
readonly [FormFieldKeyTypeId]: FormFieldKeyTypeId = FormFieldKeyTypeId
constructor(readonly path: PropertyPath.PropertyPath) {}
[Equal.symbol](that: Equal.Equal) {
return isFormFieldKey(that) && PropertyPath.equivalence(this.path, that.path)
}
[Hash.symbol]() {
return Hash.array(this.path)
}
}
export const isFormField = (u: unknown): u is FormField<unknown, unknown> => Predicate.hasProperty(u, FormFieldTypeId)
const isFormFieldKey = (u: unknown): u is FormFieldKey => Predicate.hasProperty(u, FormFieldKeyTypeId)
export const makeFormField = <A, I, R, MA, ME, MR, MP, const P extends PropertyPath.Paths<NoInfer<I>>>(
self: Form<A, I, R, MA, ME, MR, MP>,
path: P,
): FormField<PropertyPath.ValueFromPath<A, P>, PropertyPath.ValueFromPath<I, P>> => {
return new FormFieldImpl(
Subscribable.mapEffect(self.value, Option.match({
onSome: v => Option.map(PropertyPath.get(v, path), Option.some),
onNone: () => Option.some(Option.none()),
})),
SubscriptionSubRef.makeFromPath(self.encodedValue, path),
Subscribable.mapEffect(self.error, Option.match({
onSome: flow(
ParseResult.ArrayFormatter.formatError,
Effect.map(Array.filter(issue => PropertyPath.equivalence(issue.path, path))),
),
onNone: () => Effect.succeed([]),
})),
Subscribable.map(self.validationFiber, Option.isSome),
Subscribable.map(self.mutation.result, result => Result.isRunning(result) || Result.hasRefreshingFlag(result)),
) )
}
export const focusObjectField: {
<P extends readonly PropertyKey[], A extends object, I extends object, ER, EW, K extends keyof A & keyof I>(
self: Form<P, A, I, ER, EW>,
key: K,
): Form<readonly [...P, K], A[K], I[K], ER, EW>
<P extends readonly PropertyKey[], A extends object, I extends object, ER, EW, K extends keyof A & keyof I>(
key: K,
): (self: Form<P, A, I, ER, EW>) => Form<readonly [...P, K], A[K], I[K], ER, EW>
} = Function.dual(2, <P extends readonly PropertyKey[], A extends object, I extends object, ER, EW, K extends keyof A & keyof I>(
self: Form<P, A, I, ER, EW>,
key: K,
): Form<readonly [...P, K], A[K], I[K], ER, EW> => {
const form = self as FormImpl<P, A, I, ER, EW>
const path = [...form.path, key] as const
return new FormImpl(
path,
Subscribable.mapOption(form.value, a => a[key]),
Lens.focusObjectField(form.encodedValue, key),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating,
form.canSubmit,
form.isSubmitting,
)
})
export const focusArrayAt: {
<P extends readonly PropertyKey[], A extends readonly any[], I extends readonly any[], ER, EW>(
self: Form<P, A, I, ER, EW>,
index: number,
): Form<readonly [...P, number], A[number], I[number], ER | Cause.NoSuchElementException, EW | Cause.NoSuchElementException>
<P extends readonly PropertyKey[], A extends readonly any[], I extends readonly any[], ER, EW>(
index: number,
): (self: Form<P, A, I, ER, EW>) => Form<readonly [...P, number], A[number], I[number], ER | Cause.NoSuchElementException, EW | Cause.NoSuchElementException>
} = Function.dual(2, <P extends readonly PropertyKey[], A extends readonly any[], I extends readonly any[], ER, EW>(
self: Form<P, A, I, ER, EW>,
index: number,
): Form<readonly [...P, number], A[number], I[number], ER | Cause.NoSuchElementException, EW | Cause.NoSuchElementException> => {
const form = self as FormImpl<P, A, I, ER, EW>
const path = [...form.path, index] as const
return new FormImpl(
path,
Subscribable.mapOptionEffect(form.value, Array.get(index)),
Lens.focusArrayAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating,
form.canSubmit,
form.isSubmitting,
)
})
export const focusTupleAt: {
<P extends readonly PropertyKey[], A extends readonly [any, ...any[]], I extends readonly [any, ...any[]], ER, EW, K extends number>(
self: Form<P, A, I, ER, EW>,
index: K,
): Form<readonly [...P, K], A[K], I[K], ER, EW>
<P extends readonly PropertyKey[], A extends readonly [any, ...any[]], I extends readonly [any, ...any[]], ER, EW, K extends number>(
index: K,
): (self: Form<P, A, I, ER, EW>) => Form<readonly [...P, K], A[K], I[K], ER, EW>
} = Function.dual(2, <P extends readonly PropertyKey[], A extends readonly [any, ...any[]], I extends readonly [any, ...any[]], ER, EW, K extends number>(
self: Form<P, A, I, ER, EW>,
index: K,
): Form<readonly [...P, K], A[K], I[K], ER, EW> => {
const form = self as FormImpl<P, A, I, ER, EW>
const path = [...form.path, index] as const
return new FormImpl(
path,
Subscribable.mapOption(form.value, Array.unsafeGet(index)),
Lens.focusTupleAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating,
form.canSubmit,
form.isSubmitting,
)
})
export const focusChunkAt: {
<P extends readonly PropertyKey[], A, I, ER, EW>(
self: Form<P, Chunk.Chunk<A>, Chunk.Chunk<I>, ER, EW>,
index: number,
): Form<readonly [...P, number], A, I, ER | Cause.NoSuchElementException, EW>
<P extends readonly PropertyKey[], A, I, ER, EW>(
index: number,
): (self: Form<P, Chunk.Chunk<A>, Chunk.Chunk<I>, ER, EW>) => Form<readonly [...P, number], A, I, ER | Cause.NoSuchElementException, EW>
} = Function.dual(2, <P extends readonly PropertyKey[], A, I, ER, EW>(
self: Form<P, Chunk.Chunk<A>, Chunk.Chunk<I>, ER, EW>,
index: number,
): Form<readonly [...P, number], A, I, ER | Cause.NoSuchElementException, EW> => {
const form = self as FormImpl<P, Chunk.Chunk<A>, Chunk.Chunk<I>, ER, EW>
const path = [...form.path, index] as const
return new FormImpl(
path,
Subscribable.mapOptionEffect(form.value, Chunk.get(index)),
Lens.focusChunkAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating,
form.canSubmit,
form.isSubmitting,
)
})
export namespace useInput { export namespace useInput {
@@ -301,33 +362,39 @@ export namespace useInput {
} }
} }
export const useInput = Effect.fnUntraced(function* <A, I>( export const useInput = Effect.fnUntraced(function* <P extends readonly PropertyKey[], A, I, ER, EW>(
field: FormField<A, I>, form: Form<P, A, I, ER, EW>,
options?: useInput.Options, options?: useInput.Options,
): Effect.fn.Return<useInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> { ): Effect.fn.Return<useInput.Success<I>, ER, Scope.Scope> {
const internalValueRef = yield* Component.useOnChange(() => Effect.tap( const internalValueLens = yield* Component.useOnChange(() => Effect.gen(function*() {
Effect.andThen(field.encodedValue, SubscriptionRef.make), const internalValueLens = yield* Lens.get(form.encodedValue).pipe(
internalValueRef => Effect.forkScoped(Effect.all([ Effect.flatMap(SubscriptionRef.make),
Effect.map(Lens.fromSubscriptionRef),
)
yield* Effect.forkScoped(Effect.all([
Stream.runForEach( Stream.runForEach(
Stream.drop(field.encodedValue, 1), Stream.drop(form.encodedValue.changes, 1),
upstreamEncodedValue => Effect.whenEffect( upstreamEncodedValue => Effect.whenEffect(
Ref.set(internalValueRef, upstreamEncodedValue), Lens.set(internalValueLens, upstreamEncodedValue),
Effect.andThen(internalValueRef, internalValue => !Equal.equals(upstreamEncodedValue, internalValue)), Effect.andThen(Lens.get(internalValueLens), internalValue => !Equal.equals(upstreamEncodedValue, internalValue)),
), ),
), ),
Stream.runForEach( Stream.runForEach(
internalValueRef.changes.pipe( internalValueLens.changes.pipe(
Stream.drop(1), Stream.drop(1),
Stream.changesWith(Equal.equivalence()), Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity, options?.debounce ? Stream.debounce(options.debounce) : identity,
), ),
internalValue => Ref.set(field.encodedValue, internalValue), internalValue => Lens.set(form.encodedValue, internalValue),
), ),
], { concurrency: "unbounded" })), ], { concurrency: "unbounded" }))
), [field, options?.debounce])
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef) return internalValueLens
}), [form, options?.debounce])
const [value, setValue] = yield* Lens.useState(internalValueLens)
return { value, setValue } return { value, setValue }
}) })
@@ -342,55 +409,63 @@ export namespace useOptionalInput {
} }
} }
export const useOptionalInput = Effect.fnUntraced(function* <A, I>( export const useOptionalInput = Effect.fnUntraced(function* <P extends readonly PropertyKey[], A, I, ER, EW>(
field: FormField<A, Option.Option<I>>, field: Form<P, A, Option.Option<I>, ER, EW>,
options: useOptionalInput.Options<I>, options: useOptionalInput.Options<I>,
): Effect.fn.Return<useOptionalInput.Success<I>, Cause.NoSuchElementException, Scope.Scope> { ): Effect.fn.Return<useOptionalInput.Success<I>, ER, Scope.Scope> {
const [enabledRef, internalValueRef] = yield* Component.useOnChange(() => Effect.tap( const [enabledLens, internalValueLens] = yield* Component.useOnChange(() => Effect.gen(function*() {
Effect.andThen( const [enabledLens, internalValueLens] = yield* Effect.flatMap(
field.encodedValue, Lens.get(field.encodedValue),
Option.match({ Option.match({
onSome: v => Effect.all([SubscriptionRef.make(true), SubscriptionRef.make(v)]), onSome: v => Effect.all([
onNone: () => Effect.all([SubscriptionRef.make(false), SubscriptionRef.make(options.defaultValue)]), Effect.map(SubscriptionRef.make(true), Lens.fromSubscriptionRef),
Effect.map(SubscriptionRef.make(v), Lens.fromSubscriptionRef),
]),
onNone: () => Effect.all([
Effect.map(SubscriptionRef.make(false), Lens.fromSubscriptionRef),
Effect.map(SubscriptionRef.make(options.defaultValue), Lens.fromSubscriptionRef),
]),
}), }),
), )
([enabledRef, internalValueRef]) => Effect.forkScoped(Effect.all([ yield* Effect.forkScoped(Effect.all([
Stream.runForEach( Stream.runForEach(
Stream.drop(field.encodedValue, 1), Stream.drop(field.encodedValue.changes, 1),
upstreamEncodedValue => Effect.whenEffect( upstreamEncodedValue => Effect.whenEffect(
Option.match(upstreamEncodedValue, { Option.match(upstreamEncodedValue, {
onSome: v => Effect.andThen( onSome: v => Effect.andThen(
Ref.set(enabledRef, true), Lens.set(enabledLens, true),
Ref.set(internalValueRef, v), Lens.set(internalValueLens, v),
), ),
onNone: () => Effect.andThen( onNone: () => Effect.andThen(
Ref.set(enabledRef, false), Lens.set(enabledLens, false),
Ref.set(internalValueRef, options.defaultValue), Lens.set(internalValueLens, options.defaultValue),
), ),
}), }),
Effect.andThen( Effect.andThen(
Effect.all([enabledRef, internalValueRef]), Effect.all([Lens.get(enabledLens), Lens.get(internalValueLens)]),
([enabled, internalValue]) => !Equal.equals(upstreamEncodedValue, enabled ? Option.some(internalValue) : Option.none()), ([enabled, internalValue]) => !Equal.equals(upstreamEncodedValue, enabled ? Option.some(internalValue) : Option.none()),
), ),
), ),
), ),
Stream.runForEach( Stream.runForEach(
enabledRef.changes.pipe( enabledLens.changes.pipe(
Stream.zipLatest(internalValueRef.changes), Stream.zipLatest(internalValueLens.changes),
Stream.drop(1), Stream.drop(1),
Stream.changesWith(Equal.equivalence()), Stream.changesWith(Equal.equivalence()),
options?.debounce ? Stream.debounce(options.debounce) : identity, options?.debounce ? Stream.debounce(options.debounce) : identity,
), ),
([enabled, internalValue]) => Ref.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()), ([enabled, internalValue]) => Lens.set(field.encodedValue, enabled ? Option.some(internalValue) : Option.none()),
), ),
], { concurrency: "unbounded" })), ], { concurrency: "unbounded" }))
), [field, options.debounce])
const [enabled, setEnabled] = yield* SubscriptionRef.useSubscriptionRefState(enabledRef) return [enabledLens, internalValueLens] as const
const [value, setValue] = yield* SubscriptionRef.useSubscriptionRefState(internalValueRef) }), [field, options.debounce])
const [enabled, setEnabled] = yield* Lens.useState(enabledLens)
const [value, setValue] = yield* Lens.useState(internalValueLens)
return { enabled, setEnabled, value, setValue } return { enabled, setEnabled, value, setValue }
}) })
+63
View File
@@ -0,0 +1,63 @@
import { Effect, Equivalence, Stream } from "effect"
import { Lens } from "effect-lens"
import * as React from "react"
import * as Component from "./Component.js"
import * as SetStateAction from "./SetStateAction.js"
import * as SubscriptionRef from "./SubscriptionRef.js"
export * from "effect-lens/Lens"
export declare namespace useState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useState = Effect.fnUntraced(function* <A, ER, EW, RR, RW>(
lens: Lens.Lens<A, ER, EW, RR, RW>,
options?: useState.Options<NoInfer<A>>,
): Effect.fn.Return<readonly [A, React.Dispatch<React.SetStateAction<A>>], ER, RR | RW> {
const [reactStateValue, setReactStateValue] = React.useState(yield* Component.useOnMount(() => Lens.get(lens)))
yield* Component.useReactEffect(() => Effect.forkScoped(
Stream.runForEach(
Stream.changesWith(lens.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setReactStateValue(v)),
)
), [lens])
const setValue = yield* Component.useCallbackSync(
(setStateAction: React.SetStateAction<A>) => Effect.andThen(
Lens.updateAndGet(lens, prevState => SetStateAction.value(setStateAction, prevState)),
v => setReactStateValue(v),
),
[lens],
)
return [reactStateValue, setValue]
})
export declare namespace useFromState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useFromState = Effect.fnUntraced(function* <A>(
[value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>],
options?: useFromState.Options<NoInfer<A>>,
): Effect.fn.Return<Lens.Lens<A, never, never, never, never>> {
const lens = yield* Component.useOnMount(() => Effect.map(
SubscriptionRef.make(value),
Lens.fromSubscriptionRef,
))
yield* Component.useReactEffect(() => Effect.forkScoped(Stream.runForEach(
Stream.changesWith(lens.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setValue(v)),
)), [setValue])
yield* Component.useReactEffect(() => Lens.set(lens, value), [value])
return lens
})
+3 -3
View File
@@ -111,12 +111,12 @@ export declare namespace make {
export const make = Effect.fnUntraced(function* <const K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never>( export const make = Effect.fnUntraced(function* <const K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never>(
options: make.Options<K, A, E, R, P> options: make.Options<K, A, E, R, P>
): Effect.fn.Return< ): Effect.fn.Return<
Mutation<K, A, E, Result.forkEffect.OutputContext<A, E, R, P>, P>, Mutation<K, A, E, Result.forkEffect.OutputContext<R, P>, P>,
never, never,
Scope.Scope | Result.forkEffect.OutputContext<A, E, R, P> Scope.Scope | Result.forkEffect.OutputContext<R, P>
> { > {
return new MutationImpl( return new MutationImpl(
yield* Effect.context<Scope.Scope | Result.forkEffect.OutputContext<A, E, R, P>>(), yield* Effect.context<Scope.Scope | Result.forkEffect.OutputContext<R, P>>(),
options.f as any, options.f as any,
options.initialProgress as P, options.initialProgress as P,
@@ -0,0 +1,67 @@
import { describe, expect, test } from "bun:test"
import { Option } from "effect"
import * as PropertyPath from "./PropertyPath.js"
describe("immutableSet with arrays", () => {
test("sets a top-level array element", () => {
const arr = [1, 2, 3]
const result = PropertyPath.immutableSet(arr, [1], 99)
expect(result).toEqual(Option.some([1, 99, 3]))
})
test("does not mutate the original array", () => {
const arr = [1, 2, 3]
PropertyPath.immutableSet(arr, [0], 42)
expect(arr).toEqual([1, 2, 3])
})
test("sets the first element of an array", () => {
const arr = ["a", "b", "c"]
const result = PropertyPath.immutableSet(arr, [0], "z")
expect(result).toEqual(Option.some(["z", "b", "c"]))
})
test("sets the last element of an array", () => {
const arr = [10, 20, 30]
const result = PropertyPath.immutableSet(arr, [2], 99)
expect(result).toEqual(Option.some([10, 20, 99]))
})
test("sets a nested array element inside an object", () => {
const obj = { tags: ["foo", "bar", "baz"] }
const result = PropertyPath.immutableSet(obj, ["tags", 1], "qux")
expect(result).toEqual(Option.some({ tags: ["foo", "qux", "baz"] }))
})
test("sets a deeply nested value inside an array of objects", () => {
const obj = { items: [{ name: "alice" }, { name: "bob" }] }
const result = PropertyPath.immutableSet(obj, ["items", 0, "name"], "charlie")
expect(result).toEqual(Option.some({ items: [{ name: "charlie" }, { name: "bob" }] }))
})
test("sets a value in a nested array", () => {
const matrix = [[1, 2], [3, 4]]
const result = PropertyPath.immutableSet(matrix, [1, 0], 99)
expect(result).toEqual(Option.some([[1, 2], [99, 4]]))
})
test("returns Option.none() for an out-of-bounds index", () => {
const arr = [1, 2, 3]
const result = PropertyPath.immutableSet(arr, [5], 99)
expect(result).toEqual(Option.none())
})
test("returns Option.none() for a non-numeric key on an array", () => {
const arr = [1, 2, 3]
// @ts-expect-error intentionally wrong key type
const result = PropertyPath.immutableSet(arr, ["length"], 0)
expect(result).toEqual(Option.none())
})
test("empty path returns Option.some of the value itself", () => {
const arr = [1, 2, 3]
const result = PropertyPath.immutableSet(arr, [], [9, 9, 9] as any)
expect(result).toEqual(Option.some([9, 9, 9]))
})
})
+6 -6
View File
@@ -281,14 +281,14 @@ export declare namespace make {
export const make = Effect.fnUntraced(function* <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>( export const make = Effect.fnUntraced(function* <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>(
options: make.Options<K, A, KE, KR, E, R, P> options: make.Options<K, A, KE, KR, E, R, P>
): Effect.fn.Return< ): Effect.fn.Return<
Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<A, E, R, P>, P>, Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>,
never, never,
Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<A, E, R, P> Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>
> { > {
const client = yield* QueryClient.QueryClient const client = yield* QueryClient.QueryClient
return new QueryImpl<K, A, KE, KR, E, Result.forkEffect.OutputContext<A, E, R, P>, P>( return new QueryImpl<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>(
yield* Effect.context<Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<A, E, R, P>>(), yield* Effect.context<Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>>(),
options.key, options.key,
options.f as any, options.f as any,
options.initialProgress as P, options.initialProgress as P,
@@ -308,9 +308,9 @@ export const make = Effect.fnUntraced(function* <K extends Query.AnyKey, A, KE =
export const service = <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>( export const service = <K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never>(
options: make.Options<K, A, KE, KR, E, R, P> options: make.Options<K, A, KE, KR, E, R, P>
): Effect.Effect< ): Effect.Effect<
Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<A, E, R, P>, P>, Query<K, A, KE, KR, E, Result.forkEffect.OutputContext<R, P>, P>,
never, never,
Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<A, E, R, P> Scope.Scope | QueryClient.QueryClient | KR | Result.forkEffect.OutputContext<R, P>
> => Effect.tap( > => Effect.tap(
make(options), make(options),
query => Effect.forkScoped(query.run), query => Effect.forkScoped(query.run),
+75 -85
View File
@@ -1,4 +1,5 @@
import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, Pipeable, Predicate, PubSub, pipe, Ref, type Scope, Stream, Subscribable } from "effect" import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, pipe, Pipeable, Predicate, PubSub, Ref, type Scope, Stream, type Subscribable, SynchronizedRef } from "effect"
import { Lens } from "effect-lens"
export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result") export const ResultTypeId: unique symbol = Symbol.for("@effect-fc/Result/Result")
@@ -15,10 +16,6 @@ export type Final<A, E = never, P = never> = (Success<A> | Failure<E>) & ({} | F
export type Flags<P = never> = WillFetch | WillRefresh | Refreshing<P> export type Flags<P = never> = WillFetch | WillRefresh | Refreshing<P>
export declare namespace Result { export declare namespace Result {
export interface Prototype extends Pipeable.Pipeable, Equal.Equal {
readonly [ResultTypeId]: ResultTypeId
}
export type Success<R extends Result<any, any, any>> = [R] extends [Result<infer A, infer _E, infer _P>] ? A : never export type Success<R extends Result<any, any, any>> = [R] extends [Result<infer A, infer _E, infer _P>] ? A : never
export type Failure<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer E, infer _P>] ? E : never export type Failure<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer E, infer _P>] ? E : never
export type Progress<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer _E, infer P>] ? P : never export type Progress<R extends Result<any, any, any>> = [R] extends [Result<infer _A, infer _E, infer P>] ? P : never
@@ -28,21 +25,21 @@ export declare namespace Flags {
export type Keys = keyof WillFetch & WillRefresh & Refreshing<any> export type Keys = keyof WillFetch & WillRefresh & Refreshing<any>
} }
export interface Initial extends Result.Prototype { export interface Initial extends ResultPrototype {
readonly _tag: "Initial" readonly _tag: "Initial"
} }
export interface Running<P = never> extends Result.Prototype { export interface Running<P = never> extends ResultPrototype {
readonly _tag: "Running" readonly _tag: "Running"
readonly progress: P readonly progress: P
} }
export interface Success<A> extends Result.Prototype { export interface Success<A> extends ResultPrototype {
readonly _tag: "Success" readonly _tag: "Success"
readonly value: A readonly value: A
} }
export interface Failure<E = never> extends Result.Prototype { export interface Failure<E = never> extends ResultPrototype {
readonly _tag: "Failure" readonly _tag: "Failure"
readonly cause: Cause.Cause<E> readonly cause: Cause.Cause<E>
} }
@@ -61,7 +58,11 @@ export interface Refreshing<P = never> {
} }
const ResultPrototype = Object.freeze({ export interface ResultPrototype extends Pipeable.Pipeable, Equal.Equal {
readonly [ResultTypeId]: ResultTypeId
}
export const ResultPrototype: ResultPrototype = Object.freeze({
...Pipeable.Prototype, ...Pipeable.Prototype,
[ResultTypeId]: ResultTypeId, [ResultTypeId]: ResultTypeId,
@@ -95,7 +96,7 @@ const ResultPrototype = Object.freeze({
Hash.cached(this), Hash.cached(this),
) )
}, },
} as const satisfies Result.Prototype) } as const)
export const isResult = (u: unknown): u is Result<unknown, unknown, unknown> => Predicate.hasProperty(u, ResultTypeId) export const isResult = (u: unknown): u is Result<unknown, unknown, unknown> => Predicate.hasProperty(u, ResultTypeId)
@@ -162,52 +163,40 @@ export const toExit: {
} }
export interface State<A, E = never, P = never> {
readonly get: Effect.Effect<Result<A, E, P>>
readonly set: (v: Result<A, E, P>) => Effect.Effect<void>
}
export const State = <A, E = never, P = never>(): Context.Tag<State<A, E, P>, State<A, E, P>> => Context.GenericTag("@effect-fc/Result/State")
export interface Progress<P = never> { export interface Progress<P = never> {
readonly update: <E, R>( readonly progress: Lens.Lens<P, PreviousResultNotRunningNorRefreshing, never, never, never>
f: (previous: P) => Effect.Effect<P, E, R>
) => Effect.Effect<void, PreviousResultNotRunningNorRefreshing | E, R>
} }
export const Progress = <P = never>(): Context.Tag<Progress<P>, Progress<P>> => Context.GenericTag("@effect-fc/Result/Progress")
export class PreviousResultNotRunningNorRefreshing extends Data.TaggedError("@effect-fc/Result/PreviousResultNotRunningNorRefreshing")<{ export class PreviousResultNotRunningNorRefreshing extends Data.TaggedError("@effect-fc/Result/PreviousResultNotRunningNorRefreshing")<{
readonly previous: Result<unknown, unknown, unknown> readonly previous: Result<unknown, unknown, unknown>
}> {} }> {}
export const Progress = <P = never>(): Context.Tag<Progress<P>, Progress<P>> => Context.GenericTag("@effect-fc/Result/Progress") export const makeProgressLayer = <A, E, P = never>(
state: Lens.Lens<Result<A, E, P>, never, never, never, never>
export const makeProgressLayer = <A, E, P = never>(): Layer.Layer< ): Layer.Layer<Progress<P> | Progress<never>, never, never> => Layer.succeed(
Progress<P>, Progress<P>() as Context.Tag<Progress<P> | Progress<never>, Progress<P> | Progress<never>>,
never, {
State<A, E, P> progress: state.pipe(
> => Layer.effect(Progress<P>(), Effect.gen(function*() { Lens.mapEffect(
const state = yield* State<A, E, P>() a => (isRunning(a) || hasRefreshingFlag(a))
? Effect.succeed(a)
return { : Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous: a })),
update: <FE, FR>(f: (previous: P) => Effect.Effect<P, FE, FR>) => Effect.Do.pipe( (_, b) => Effect.succeed(b),
Effect.bind("previous", () => Effect.andThen(state.get, previous =>
(isRunning(previous) || hasRefreshingFlag(previous))
? Effect.succeed(previous)
: Effect.fail(new PreviousResultNotRunningNorRefreshing({ previous })),
)),
Effect.bind("progress", ({ previous }) => f(previous.progress)),
Effect.let("next", ({ previous, progress }) => isRunning(previous)
? running(progress)
: refreshing(previous, progress) as Final<A, E, P> & Refreshing<P>
), ),
Effect.andThen(({ next }) => state.set(next)), Lens.map(
a => a.progress,
(a, b) => isRunning(a)
? running(b)
: refreshing(a, b) as Final<A, E, P> & Refreshing<P>,
), ),
} )
})) },
)
export namespace unsafeForkEffect { export namespace unsafeForkEffect {
export type OutputContext<A, E, R, P> = Exclude<R, State<A, E, P> | Progress<P> | Progress<never>> export type OutputContext<R, P> = Exclude<R, Progress<P> | Progress<never>>
export interface Options<A, E, P> { export interface Options<A, E, P> {
readonly initial?: Initial | Final<A, E, P> readonly initial?: Initial | Final<A, E, P>
@@ -215,55 +204,56 @@ export namespace unsafeForkEffect {
} }
} }
export const unsafeForkEffect = <A, E, R, P = never>( export const unsafeForkEffect = Effect.fnUntraced(function* <A, E, R, P = never>(
effect: Effect.Effect<A, E, R>, effect: Effect.Effect<A, E, R>,
options?: unsafeForkEffect.Options<NoInfer<A>, NoInfer<E>, P>, options?: unsafeForkEffect.Options<NoInfer<A>, NoInfer<E>, P>,
): Effect.Effect< ): Effect.fn.Return<
readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>], readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>],
never, never,
Scope.Scope | unsafeForkEffect.OutputContext<A, E, R, P> Scope.Scope | unsafeForkEffect.OutputContext<R, P>
> => Effect.Do.pipe( > {
Effect.bind("ref", () => Ref.make(options?.initial ?? initial<A, E, P>())), const ref = yield* SynchronizedRef.make<Result<A, E, P>>(options?.initial ?? initial<A, E, P>())
Effect.bind("pubsub", () => PubSub.unbounded<Result<A, E, P>>()), const pubsub = yield* PubSub.unbounded<Result<A, E, P>>()
Effect.bind("fiber", ({ ref, pubsub }) => Effect.forkScoped(State<A, E, P>().pipe(
Effect.andThen(state => state.set( const state = Lens.make<Result<A, E, P>, never, never, never, never>({
(isFinal(options?.initial) && hasWillRefreshFlag(options?.initial)) get get() { return Ref.get(ref) },
? refreshing(options.initial, options?.initialProgress) as Result<A, E, P> get changes() {
: running(options?.initialProgress) return Stream.unwrapScoped(Effect.map(
).pipe(
Effect.andThen(effect),
Effect.onExit(exit => Effect.andThen(
state.set(fromExit(exit)),
Effect.forkScoped(PubSub.shutdown(pubsub)),
)),
)),
Effect.provide(Layer.empty.pipe(
Layer.provideMerge(makeProgressLayer<A, E, P>()),
Layer.provideMerge(Layer.succeed(State<A, E, P>(), {
get: Ref.get(ref),
set: v => Effect.andThen(Ref.set(ref, v), PubSub.publish(pubsub, v))
})),
)),
))),
Effect.map(({ ref, pubsub, fiber }) => [
Subscribable.make({
get: Ref.get(ref),
changes: Stream.unwrapScoped(Effect.map(
Effect.all([Ref.get(ref), Stream.fromPubSub(pubsub, { scoped: true })]), Effect.all([Ref.get(ref), Stream.fromPubSub(pubsub, { scoped: true })]),
([latest, stream]) => Stream.concat(Stream.make(latest), stream), ([latest, stream]) => Stream.concat(Stream.make(latest), stream),
))
},
modify: f => Ref.get(ref).pipe(
Effect.flatMap(f),
Effect.flatMap(([b, a]) => Ref.set(ref, a).pipe(
Effect.as(b),
Effect.zipLeft(PubSub.publish(pubsub, a))
)), )),
}), ),
fiber, })
]),
) as Effect.Effect< const fiber = yield* Effect.gen(function*() {
readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>], yield* Lens.set(
never, state,
Scope.Scope | unsafeForkEffect.OutputContext<A, E, R, P> (isFinal(options?.initial) && hasWillRefreshFlag(options?.initial))
> ? refreshing(options.initial, options?.initialProgress) as Result<A, E, P>
: running(options?.initialProgress),
)
return yield* Effect.onExit(effect, exit => Effect.andThen(
Lens.set(state, fromExit(exit)),
Effect.forkScoped(PubSub.shutdown(pubsub)),
))
}).pipe(
Effect.forkScoped,
Effect.provide(makeProgressLayer(state)),
)
return [state, fiber] as const
})
export namespace forkEffect { export namespace forkEffect {
export type InputContext<R, P> = R extends Progress<infer X> ? [X] extends [P] ? R : never : R export type InputContext<R, P> = R extends Progress<infer X> ? [X] extends [P] ? R : never : R
export type OutputContext<A, E, R, P> = unsafeForkEffect.OutputContext<A, E, R, P> export type OutputContext<R, P> = unsafeForkEffect.OutputContext<R, P>
export interface Options<A, E, P> extends unsafeForkEffect.Options<A, E, P> {} export interface Options<A, E, P> extends unsafeForkEffect.Options<A, E, P> {}
} }
@@ -274,6 +264,6 @@ export const forkEffect: {
): Effect.Effect< ): Effect.Effect<
readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>], readonly [result: Subscribable.Subscribable<Result<A, E, P>, never, never>, fiber: Fiber.Fiber<A, E>],
never, never,
Scope.Scope | forkEffect.OutputContext<A, E, R, P> Scope.Scope | forkEffect.OutputContext<R, P>
> >
} = unsafeForkEffect } = unsafeForkEffect
+1 -1
View File
@@ -3,8 +3,8 @@ import type * as React from "react"
export const value: { export const value: {
<S>(prevState: S): (self: React.SetStateAction<S>) => S
<S>(self: React.SetStateAction<S>, prevState: S): S <S>(self: React.SetStateAction<S>, prevState: S): S
<S>(prevState: S): (self: React.SetStateAction<S>) => S
} = Function.dual(2, <S>(self: React.SetStateAction<S>, prevState: S): S => } = Function.dual(2, <S>(self: React.SetStateAction<S>, prevState: S): S =>
typeof self === "function" typeof self === "function"
? (self as (prevState: S) => S)(prevState) ? (self as (prevState: S) => S)(prevState)
+4 -3
View File
@@ -1,8 +1,11 @@
import { Effect, Equivalence, Stream, Subscribable } from "effect" import { Effect, Equivalence, Stream } from "effect"
import { Subscribable } from "effect-lens"
import * as React from "react" import * as React from "react"
import * as Component from "./Component.js" import * as Component from "./Component.js"
export * from "effect-lens/Subscribable"
export const zipLatestAll = <const T extends readonly Subscribable.Subscribable<any, any, any>[]>( export const zipLatestAll = <const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
...elements: T ...elements: T
): Subscribable.Subscribable< ): Subscribable.Subscribable<
@@ -48,5 +51,3 @@ export const useSubscribables = Effect.fnUntraced(function* <const T extends rea
return reactStateValue as any return reactStateValue as any
}) })
export * from "effect/Subscribable"
@@ -0,0 +1,183 @@
import { describe, expect, test } from "bun:test"
import { Chunk, Effect, Ref, SubscriptionRef } from "effect"
import * as SubscriptionSubRef from "./SubscriptionSubRef.js"
describe("SubscriptionSubRef with array refs", () => {
test("creates a subref for a single array element using path", async () => {
const value = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"])
return subref.get
},
),
)
expect(value).toBe("bob")
})
test("modifies a single array element via subref", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([{ name: "alice" }, { name: "bob" }, { name: "charlie" }]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "name"])
return Effect.flatMap(
Ref.set(subref, "bob-updated"),
() => Ref.get(parent),
)
},
),
)
expect(result).toEqual([{ name: "alice" }, { name: "bob-updated" }, { name: "charlie" }])
})
test("modifies array element at index 0", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([10, 20, 30]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [0])
return Effect.flatMap(
Ref.set(subref, 99),
() => Ref.get(parent),
)
},
),
)
expect(result).toEqual([99, 20, 30])
})
test("modifies array element at last index", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(["a", "b", "c"]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [2])
return Effect.flatMap(
Ref.set(subref, "z"),
() => Ref.get(parent),
)
},
),
)
expect(result).toEqual(["a", "b", "z"])
})
test("modifies nested array element", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([[1, 2], [3, 4], [5, 6]]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [1, 0])
return Effect.flatMap(
Ref.set(subref, 99),
() => Ref.get(parent),
)
},
),
)
expect(result).toEqual([[1, 2], [99, 4], [5, 6]])
})
test("uses modifyEffect to transform array element", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([{ count: 1 }, { count: 2 }, { count: 3 }]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [1, "count"])
return Effect.flatMap(
Ref.update(subref, count => count + 100),
() => Effect.map(Ref.get(parent), parentValue => ({ result: 102, parentValue })),
)
},
),
)
expect(result.result).toBe(102) // count + 100
expect(result.parentValue).toEqual([{ count: 1 }, { count: 102 }, { count: 3 }]) // count + 100
})
test("uses modify to transform array element", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([10, 20, 30]),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [1])
return Effect.flatMap(
Ref.update(subref, x => x + 5),
() => Effect.map(Ref.get(parent), parentValue => ({ result: 25, parentValue })),
)
},
),
)
expect(result.result).toBe(25) // 20 + 5
expect(result.parentValue).toEqual([10, 25, 30]) // 20 + 5
})
test("makeFromChunkIndex modifies chunk element", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(Chunk.make(100, 200, 300)),
parent => {
const subref = SubscriptionSubRef.makeFromChunkIndex(parent, 1)
return Effect.flatMap(
Ref.set(subref, 999),
() => Ref.get(parent),
)
},
),
)
expect(Chunk.toReadonlyArray(result)).toEqual([100, 999, 300])
})
test("makeFromGetSet with custom getter/setter for array element", async () => {
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make([{ id: 1, value: "a" }, { id: 2, value: "b" }]),
parent => {
const subref = SubscriptionSubRef.makeFromGetSet(parent, {
get: arr => arr[0].value,
set: (arr, newValue) => [
{ ...arr[0], value: newValue },
...arr.slice(1),
],
})
return Effect.flatMap(
Ref.set(subref, "updated"),
() => Ref.get(parent),
)
},
),
)
expect(result).toEqual([{ id: 1, value: "updated" }, { id: 2, value: "b" }])
})
test("does not mutate original array when modifying via subref", async () => {
const original = [{ name: "alice" }, { name: "bob" }]
const result = await Effect.runPromise(
Effect.flatMap(
SubscriptionRef.make(original),
parent => {
const subref = SubscriptionSubRef.makeFromPath(parent, [0, "name"])
return Effect.flatMap(
Ref.set(subref, "alice-updated"),
() => Ref.get(parent),
)
},
),
)
expect(original).toEqual([{ name: "alice" }, { name: "bob" }]) // original unchanged
expect(result).toEqual([{ name: "alice-updated" }, { name: "bob" }]) // new value in ref
})
})
+1
View File
@@ -2,6 +2,7 @@ export * as Async from "./Async.js"
export * as Component from "./Component.js" export * as Component from "./Component.js"
export * as ErrorObserver from "./ErrorObserver.js" export * as ErrorObserver from "./ErrorObserver.js"
export * as Form from "./Form.js" export * as Form from "./Form.js"
export * as Lens from "./Lens.js"
export * as Memoized from "./Memoized.js" export * as Memoized from "./Memoized.js"
export * as Mutation from "./Mutation.js" export * as Mutation from "./Mutation.js"
export * as PropertyPath from "./PropertyPath.js" export * as PropertyPath from "./PropertyPath.js"
+3 -1
View File
@@ -25,6 +25,7 @@
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,
// Build // Build
"rootDir": "./src",
"outDir": "./dist", "outDir": "./dist",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
@@ -34,5 +35,6 @@
] ]
}, },
"include": ["./src"] "include": ["./src"],
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
} }
+18 -18
View File
@@ -13,30 +13,30 @@
"clean:modules": "rm -rf node_modules" "clean:modules": "rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/react-router": "^1.154.12", "@tanstack/react-router": "^1.168.3",
"@tanstack/react-router-devtools": "^1.154.12", "@tanstack/react-router-devtools": "^1.166.11",
"@tanstack/router-plugin": "^1.154.12", "@tanstack/router-plugin": "^1.167.4",
"@types/react": "^19.2.9", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2", "@vitejs/plugin-react": "^6.0.1",
"globals": "^17.0.0", "globals": "^17.4.0",
"react": "^19.2.3", "react": "^19.2.4",
"react-dom": "^19.2.3", "react-dom": "^19.2.4",
"type-fest": "^5.4.1", "type-fest": "^5.5.0",
"vite": "^7.3.1" "vite": "^8.0.2"
}, },
"dependencies": { "dependencies": {
"@effect/platform": "^0.94.2", "@effect/platform": "^0.96.0",
"@effect/platform-browser": "^0.74.0", "@effect/platform-browser": "^0.76.0",
"@radix-ui/themes": "^3.2.1", "@radix-ui/themes": "^3.3.0",
"@typed/id": "^0.17.2", "@typed/id": "^0.17.2",
"effect": "^3.19.15", "effect": "^3.21.0",
"effect-fc": "workspace:*", "effect-fc": "workspace:*",
"react-icons": "^5.5.0" "react-icons": "^5.6.0"
}, },
"overrides": { "overrides": {
"@types/react": "^19.2.9", "@types/react": "^19.2.14",
"effect": "^3.19.15", "effect": "^3.21.0",
"react": "^19.2.3" "react": "^19.2.4"
} }
} }
@@ -1,24 +1,22 @@
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes" import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
import { Array, Option } from "effect" import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldFormInputView { export declare namespace TextFieldFormInputView {
export interface Props export interface Props extends Omit<TextField.RootProps, "form">, Form.useInput.Options {
extends TextField.RootProps, Form.useInput.Options { readonly form: Form.Form<readonly PropertyKey[], any, string>
readonly field: Form.FormField<any, string>
} }
} }
export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*( export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(
props: TextFieldFormInputView.Props props: TextFieldFormInputView.Props
) { ) {
const input = yield* Form.useInput(props.field, props) const input = yield* Form.useInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues, props.form.issues,
props.field.isValidating, props.form.isValidating,
props.field.isSubmitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -27,7 +25,7 @@ export class TextFieldFormInputView extends Component.make("TextFieldFormInputVi
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={isSubmitting} disabled={isSubmitting}
{...props} {...Struct.omit(props, "form")}
> >
{isValidating && {isValidating &&
<TextField.Slot side="right"> <TextField.Slot side="right">
@@ -4,21 +4,19 @@ import { Component, Form, Subscribable } from "effect-fc"
export declare namespace TextFieldOptionalFormInputView { export declare namespace TextFieldOptionalFormInputView {
export interface Props export interface Props extends Omit<TextField.RootProps, "form" | "defaultValue">, Form.useOptionalInput.Options<string> {
extends Omit<TextField.RootProps, "defaultValue">, Form.useOptionalInput.Options<string> { readonly form: Form.Form<readonly PropertyKey[], any, Option.Option<string>>
readonly field: Form.FormField<any, Option.Option<string>>
} }
} }
export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*( export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*(
props: TextFieldOptionalFormInputView.Props props: TextFieldOptionalFormInputView.Props
) { ) {
const input = yield* Form.useOptionalInput(props.field, props) const input = yield* Form.useOptionalInput(props.form, props)
const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.field.issues, props.form.issues,
props.field.isValidating, props.form.isValidating,
props.field.isSubmitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -27,7 +25,7 @@ export class TextFieldOptionalFormInputView extends Component.make("TextFieldOpt
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={!input.enabled || isSubmitting} disabled={!input.enabled || isSubmitting}
{...Struct.omit(props, "defaultValue")} {...Struct.omit(props, "form", "defaultValue")}
> >
<TextField.Slot side="left"> <TextField.Slot side="left">
<Switch <Switch
+22 -12
View File
@@ -1,11 +1,11 @@
import { Button, Container, Flex, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Match, Option, ParseResult, Schema } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView" import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView" import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { DateTimeUtcFromZonedInput } from "@/lib/schema" import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
import { Button, Container, Flex, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Match, Option, ParseResult, Schema } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
const email = Schema.pattern<typeof Schema.String>( const email = Schema.pattern<typeof Schema.String>(
@@ -40,7 +40,8 @@ const RegisterFormSubmitSchema = Schema.Struct({
}) })
class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", { class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", {
scoped: Form.service({ scoped: Effect.gen(function*() {
const form = yield* Form.service({
schema: RegisterFormSchema.pipe( schema: RegisterFormSchema.pipe(
Schema.compose( Schema.compose(
Schema.transformOrFail( Schema.transformOrFail(
@@ -59,15 +60,22 @@ class RegisterFormService extends Effect.Service<RegisterFormService>()("Registe
yield* Effect.sleep("500 millis") yield* Effect.sleep("500 millis")
return yield* Schema.decode(RegisterFormSubmitSchema)(value) return yield* Schema.decode(RegisterFormSubmitSchema)(value)
}), }),
debounce: "500 millis", })
return {
form,
emailField: Form.focusObjectField(form, "email"),
passwordField: Form.focusObjectField(form, "password"),
birthField: Form.focusObjectField(form, "birth"),
} as const
}) })
}) {} }) {}
class RegisterFormView extends Component.make("RegisterFormView")(function*() { class RegisterFormView extends Component.make("RegisterFormView")(function*() {
const form = yield* RegisterFormService const form = yield* RegisterFormService
const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([ const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([
form.canSubmit, form.form.canSubmit,
form.mutation.result, form.form.mutation.result,
]) ])
const runPromise = yield* Component.useRunPromise() const runPromise = yield* Component.useRunPromise()
@@ -84,20 +92,22 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
<Container width="300"> <Container width="300">
<form onSubmit={e => { <form onSubmit={e => {
e.preventDefault() e.preventDefault()
void runPromise(form.submit) void runPromise(form.form.submit)
}}> }}>
<Flex direction="column" gap="2"> <Flex direction="column" gap="2">
<TextFieldFormInput <TextFieldFormInput
field={yield* form.field(["email"])} form={form.emailField}
debounce="250 millis"
/> />
<TextFieldFormInput <TextFieldFormInput
field={yield* form.field(["password"])} form={form.passwordField}
debounce="250 millis"
/> />
<TextFieldOptionalFormInput <TextFieldOptionalFormInput
type="datetime-local" type="datetime-local"
field={yield* form.field(["birth"])} form={form.birthField}
defaultValue="" defaultValue=""
/> />
+14 -12
View File
@@ -1,13 +1,13 @@
import { Box, Button, Flex, IconButton } from "@radix-ui/themes"
import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Chunk, type DateTime, Effect, Match, Option, Ref, Schema, Stream } from "effect"
import { Component, Form, Subscribable } from "effect-fc"
import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6"
import * as Domain from "@/domain" import * as Domain from "@/domain"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView" import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView" import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { DateTimeUtcFromZonedInput } from "@/lib/schema" import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { Box, Button, Flex, IconButton } from "@radix-ui/themes"
import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Chunk, type DateTime, Effect, Match, Option, Ref, Schema, Stream } from "effect"
import { Component, Form, Lens, Subscribable } from "effect-fc"
import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6"
import { TodosState } from "./TodosState" import { TodosState } from "./TodosState"
@@ -59,20 +59,19 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
Match.tag("new", () => Ref.update(state.ref, Chunk.prepend(todo)).pipe( Match.tag("new", () => Ref.update(state.ref, Chunk.prepend(todo)).pipe(
Effect.andThen(makeTodo), Effect.andThen(makeTodo),
Effect.andThen(Schema.encode(TodoFormSchema)), Effect.andThen(Schema.encode(TodoFormSchema)),
Effect.andThen(v => Ref.set(form.encodedValue, v)), Effect.andThen(v => Lens.set(form.encodedValue, v)),
)), )),
Match.tag("edit", ({ id }) => Ref.set(state.getElementRef(id), todo)), Match.tag("edit", ({ id }) => Ref.set(state.getElementRef(id), todo)),
Match.exhaustive, Match.exhaustive,
), ),
autosubmit: props._tag === "edit", autosubmit: props._tag === "edit",
debounce: "250 millis",
}) })
return [ return [
indexRef, indexRef,
form, form,
yield* form.field(["content"]), Form.focusObjectField(form, "content"),
yield* form.field(["completedAt"]), Form.focusObjectField(form, "completedAt"),
] as const ] as const
}), [props._tag, props._tag === "edit" ? props.id : undefined]) }), [props._tag, props._tag === "edit" ? props.id : undefined])
@@ -92,11 +91,14 @@ export class TodoView extends Component.make("TodoView")(function*(props: TodoPr
<Flex direction="row" align="center" gap="2"> <Flex direction="row" align="center" gap="2">
<Box flexGrow="1"> <Box flexGrow="1">
<Flex direction="column" align="stretch" gap="2"> <Flex direction="column" align="stretch" gap="2">
<TextFieldFormInput field={contentField} /> <TextFieldFormInput
form={contentField}
debounce="250 millis"
/>
<Flex direction="row" justify="center" align="center" gap="2"> <Flex direction="row" justify="center" align="center" gap="2">
<TextFieldOptionalFormInput <TextFieldOptionalFormInput
field={completedAtField} form={completedAtField}
type="datetime-local" type="datetime-local"
defaultValue="" defaultValue=""
/> />