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
39 changed files with 1433 additions and 1240 deletions
View File
+291 -358
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.14", "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.4.16", "@biomejs/biome": "^2.4.9",
"@effect/language-service": "^0.86.2", "@effect/language-service": "^0.84.2",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.11",
"npm-check-updates": "^22.2.1", "npm-check-updates": "^19.6.6",
"npm-sort": "^0.0.4", "npm-sort": "^0.0.4",
"turbo": "^2.9.16", "turbo": "^2.8.21",
"typescript": "^6.0.3" "typescript": "^6.0.2"
} }
} }
@@ -7,4 +7,6 @@ tags: [hola, docusaurus]
Lorem ipsum dolor sit amet... Lorem ipsum dolor sit amet...
<!-- truncate -->
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet ...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
@@ -9,6 +9,8 @@ This is the summary of a very long blog post,
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view. Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
<!-- truncate -->
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
@@ -9,6 +9,8 @@ tags: [facebook, hello, docusaurus]
Here are a few tips you might find useful. Here are a few tips you might find useful.
<!-- truncate -->
Simply add Markdown files (or folders) to the `blog` directory. Simply add Markdown files (or folders) to the `blog` directory.
Regular blog authors can be added to `authors.yml`. Regular blog authors can be added to `authors.yml`.
+11 -12
View File
@@ -15,20 +15,19 @@
"typecheck": "tsc" "typecheck": "tsc"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "3.10.1", "@docusaurus/core": "3.9.2",
"@docusaurus/faster": "^3.10.1", "@docusaurus/preset-classic": "3.9.2",
"@docusaurus/preset-classic": "3.10.1", "@mdx-js/react": "^3.0.0",
"@mdx-js/react": "^3.1.1", "clsx": "^2.0.0",
"clsx": "^2.1.1", "prism-react-renderer": "^2.3.0",
"prism-react-renderer": "^2.4.1", "react": "^19.0.0",
"react": "^19.2.5", "react-dom": "^19.0.0"
"react-dom": "^19.2.5"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "3.10.1", "@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.10.1", "@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.10.1", "@docusaurus/types": "3.9.2",
"typescript": "~6.0.3" "typescript": "~6.0.0"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "effect-fc", "name": "effect-fc",
"description": "Write React function components with Effect", "description": "Write React function components with Effect",
"version": "0.3.0", "version": "0.2.4",
"type": "module", "type": "module",
"files": [ "files": [
"./README.md", "./README.md",
@@ -46,6 +46,6 @@
"react": "^19.2.0" "react": "^19.2.0"
}, },
"dependencies": { "dependencies": {
"effect-lens": "^0.2.0" "effect-lens": "^0.1.4"
} }
} }
+7 -8
View File
@@ -37,8 +37,8 @@ export type AsyncProps = Omit<React.SuspenseProps, "children">
export const AsyncPrototype: AsyncPrototype = Object.freeze({ export const AsyncPrototype: AsyncPrototype = Object.freeze({
[AsyncTypeId]: AsyncTypeId, [AsyncTypeId]: AsyncTypeId,
asFunctionComponent<P extends {}, A extends React.ReactNode, E, R, F extends Component.Component.Signature>( asFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
this: Component.Component<P, A, E, R, F> & Async, this: Component.Component<P, A, E, R> & Async,
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>, runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
) { ) {
const Inner = (props: { readonly promise: Promise<React.ReactNode> }) => React.use(props.promise) const Inner = (props: { readonly promise: Promise<React.ReactNode> }) => React.use(props.promise)
@@ -106,7 +106,7 @@ export const isAsync = (u: unknown): u is Async => Predicate.hasProperty(u, Asyn
* ) * )
* ``` * ```
*/ */
export const async = <T extends Component.Component.Any>( export const async = <T extends Component.Component<any, any, any, any>>(
self: T & ( self: T & (
"promise" extends keyof Component.Component.Props<T> "promise" extends keyof Component.Component.Props<T>
? "The 'promise' prop name is restricted for Async components. Please rename the 'promise' prop to something else." ? "The 'promise' prop name is restricted for Async components. Please rename the 'promise' prop to something else."
@@ -118,8 +118,7 @@ export const async = <T extends Component.Component.Any>(
Component.Component.Props<T> & AsyncProps, Component.Component.Props<T> & AsyncProps,
Component.Component.Success<T>, Component.Component.Success<T>,
Component.Component.Error<T>, Component.Component.Error<T>,
Component.Component.Context<T>, Component.Component.Context<T>
Component.Component.DefaultSignature<Component.Component.Props<T> & AsyncProps, Component.Component.Success<T>>
> >
& Async & Async
) => Object.setPrototypeOf( ) => Object.setPrototypeOf(
@@ -155,14 +154,14 @@ export const async = <T extends Component.Component.Any>(
* ``` * ```
*/ */
export const withOptions: { export const withOptions: {
<T extends Component.Component.Any & Async>( <T extends Component.Component<any, any, any, any> & Async>(
options: Partial<AsyncOptions> options: Partial<AsyncOptions>
): (self: T) => T ): (self: T) => T
<T extends Component.Component.Any & Async>( <T extends Component.Component<any, any, any, any> & Async>(
self: T, self: T,
options: Partial<AsyncOptions>, options: Partial<AsyncOptions>,
): T ): T
} = Function.dual(2, <T extends Component.Component.Any & Async>( } = Function.dual(2, <T extends Component.Component<any, any, any, any> & Async>(
self: T, self: T,
options: Partial<AsyncOptions>, options: Partial<AsyncOptions>,
): T => Object.setPrototypeOf( ): T => Object.setPrototypeOf(
+59 -97
View File
@@ -10,52 +10,49 @@ export type ComponentTypeId = typeof ComponentTypeId
/** /**
* Represents an Effect-based React Component that integrates the Effect system with React. * Represents an Effect-based React Component that integrates the Effect system with React.
*/ */
export interface Component<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature> export interface Component<P extends {}, A extends React.ReactNode, E, R>
extends ComponentPrototype<R, F>, ComponentOptions { extends ComponentPrototype<P, A, R>, ComponentOptions {
new(_: never): Record<string, never> new(_: never): Record<string, never>
readonly [ComponentTypeId]: ComponentTypeId readonly [ComponentTypeId]: ComponentTypeId
readonly "~Props": P readonly "~Props": P
readonly "~Success": A readonly "~Success": A
readonly "~Error": E readonly "~Error": E
readonly "~Context": R readonly "~Context": R
readonly "~Function": F
readonly body: (props: P) => Effect.Effect<A, E, R> readonly body: (props: P) => Effect.Effect<A, E, R>
} }
export declare namespace Component { export declare namespace Component {
export type Default<P extends {}, A extends React.ReactNode, E, R> = Component<P, A, E, R, DefaultSignature<P, A>> export type Props<T extends Component<any, any, any, any>> = [T] extends [Component<infer P, infer _A, infer _E, infer _R>] ? P : never
export type Any = Component<any, any, any, any, any> export type Success<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer A, infer _E, infer _R>] ? A : never
export type Error<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer E, infer _R>] ? E : never
export type Context<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer _E, infer R>] ? R : never
export type Signature = (props: any) => React.ReactNode export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
export type DefaultSignature<P extends {}, A extends React.ReactNode> = (props: P) => A
export type Props<T extends Any> = [T] extends [Component<infer P, infer _A, infer _E, infer _R, infer _F>] ? P : never
export type Success<T extends Any> = [T] extends [Component<infer _P, infer A, infer _E, infer _R, infer _F>] ? A : never
export type Error<T extends Any> = [T] extends [Component<infer _P, infer _A, infer E, infer _R, infer _F>] ? E : never
export type Context<T extends Any> = [T] extends [Component<infer _P, infer _A, infer _E, infer R, infer _F>] ? R : never
export type Function<T extends Any> = [T] extends [Component<infer _P, infer _A, infer _E, infer _R, infer F>] ? F : never
export type AsComponent<T extends Any> = Component<Props<T>, Success<T>, Error<T>, Context<T>, Function<T>>
} }
export interface ComponentImpl<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature> export interface ComponentPrototype<P extends {}, A extends React.ReactNode, R>
extends Component<P, A, E, R, F>, ComponentImplPrototype<R, F> {} extends Pipeable.Pipeable {
readonly [ComponentTypeId]: ComponentTypeId
readonly use: Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>
export interface ComponentImplPrototype<R, F extends Component.Signature> { asFunctionComponent(
readonly use: Effect.Effect<F, never, Exclude<R, Scope.Scope>> runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>
): (props: P) => A
asFunctionComponent(runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>): F setFunctionComponentName(f: React.FC<P>): void
setFunctionComponentName(f: F): void transformFunctionComponent(f: React.FC<P>): React.FC<P>
transformFunctionComponent(f: F): F
} }
export const ComponentImplPrototype: ComponentImplPrototype<any, any> = Object.freeze({ export const ComponentPrototype: ComponentPrototype<any, any, any> = Object.freeze({
[ComponentTypeId]: ComponentTypeId,
...Pipeable.Prototype,
get use() { return use(this) }, get use() { return use(this) },
asFunctionComponent<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( asFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
this: ComponentImpl<P, A, E, R, F>, this: Component<P, A, E, R>,
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>, runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
) { ) {
return (props: P) => Runtime.runSync(runtimeRef.current)( return (props: P) => Runtime.runSync(runtimeRef.current)(
@@ -66,8 +63,8 @@ export const ComponentImplPrototype: ComponentImplPrototype<any, any> = Object.f
) )
}, },
setFunctionComponentName<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( setFunctionComponentName<P extends {}, A extends React.ReactNode, E, R>(
this: ComponentImpl<P, A, E, R, F>, this: Component<P, A, E, R>,
f: React.FC<P>, f: React.FC<P>,
) { ) {
f.displayName = this.displayName ?? "Anonymous" f.displayName = this.displayName ?? "Anonymous"
@@ -76,8 +73,8 @@ export const ComponentImplPrototype: ComponentImplPrototype<any, any> = Object.f
transformFunctionComponent: identity, transformFunctionComponent: identity,
} as const) } as const)
const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode, E, R>(
self: ComponentImpl<P, A, E, R, F> self: Component<P, A, E, R>
) { ) {
// biome-ignore lint/style/noNonNullAssertion: React ref initialization // biome-ignore lint/style/noNonNullAssertion: React ref initialization
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!) const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
@@ -85,7 +82,7 @@ const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode
return yield* React.useState(() => Runtime.runSync(runtimeRef.current)(Effect.cachedFunction( return yield* React.useState(() => Runtime.runSync(runtimeRef.current)(Effect.cachedFunction(
(_services: readonly any[]) => Effect.sync(() => { (_services: readonly any[]) => Effect.sync(() => {
const f = self.asFunctionComponent(runtimeRef) const f: React.FC<P> = self.asFunctionComponent(runtimeRef)
self.setFunctionComponentName(f) self.setFunctionComponentName(f)
return self.transformFunctionComponent(f) return self.transformFunctionComponent(f)
}), }),
@@ -96,23 +93,6 @@ const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode
}) })
export interface ComponentPrototype<R, F extends Component.Signature>
extends Pipeable.Pipeable {
readonly [ComponentTypeId]: ComponentTypeId
readonly use: Effect.Effect<F, never, Exclude<R, Scope.Scope>>
}
export const ComponentPrototype: ComponentPrototype<any, any> = Object.freeze(
Object.defineProperties(
{
[ComponentTypeId]: ComponentTypeId,
...Pipeable.Prototype,
},
Object.getOwnPropertyDescriptors(ComponentImplPrototype),
) as ComponentPrototype<any, any>
)
export interface ComponentOptions { export interface ComponentOptions {
/** /**
* Custom display name for the component in React DevTools and debugging utilities. * Custom display name for the component in React DevTools and debugging utilities.
@@ -151,13 +131,13 @@ export const defaultOptions: ComponentOptions = {
} }
export const isComponent = (u: unknown): u is Component.Default<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, ComponentTypeId) export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, ComponentTypeId)
export declare namespace make { export declare namespace make {
export type Gen = { export type Gen = {
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A extends React.ReactNode, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A extends React.ReactNode, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never> body: (props: P) => Generator<Eff, A, never>
): Component.Default< ): Component<
P, A, P, A,
[Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E : never,
[Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R : never [Eff] extends [never] ? never : [Eff] extends [Utils.YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R : never
@@ -172,7 +152,7 @@ export declare namespace make {
>, >,
props: NoInfer<P>, props: NoInfer<P>,
) => B, ) => B,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<B>>, Effect.Effect.Error<B>, Effect.Effect.Context<B>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<B>>, Effect.Effect.Error<B>, Effect.Effect.Context<B>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -184,7 +164,7 @@ export declare namespace make {
props: NoInfer<P>, props: NoInfer<P>,
) => B, ) => B,
b: (_: B, props: NoInfer<P>) => C, b: (_: B, props: NoInfer<P>) => C,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<C>>, Effect.Effect.Error<C>, Effect.Effect.Context<C>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<C>>, Effect.Effect.Error<C>, Effect.Effect.Context<C>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -197,7 +177,7 @@ export declare namespace make {
) => B, ) => B,
b: (_: B, props: NoInfer<P>) => C, b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D, c: (_: C, props: NoInfer<P>) => D,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<D>>, Effect.Effect.Error<D>, Effect.Effect.Context<D>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<D>>, Effect.Effect.Error<D>, Effect.Effect.Context<D>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -211,7 +191,7 @@ export declare namespace make {
b: (_: B, props: NoInfer<P>) => C, b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D, c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E, d: (_: D, props: NoInfer<P>) => E,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<E>>, Effect.Effect.Error<E>, Effect.Effect.Context<E>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<E>>, Effect.Effect.Error<E>, Effect.Effect.Context<E>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -226,7 +206,7 @@ export declare namespace make {
c: (_: C, props: NoInfer<P>) => D, c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E, d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F, e: (_: E, props: NoInfer<P>) => F,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<F>>, Effect.Effect.Error<F>, Effect.Effect.Context<F>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<F>>, Effect.Effect.Error<F>, Effect.Effect.Context<F>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -242,7 +222,7 @@ export declare namespace make {
d: (_: D, props: NoInfer<P>) => E, d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F, e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G, f: (_: F, props: NoInfer<P>) => G,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<G>>, Effect.Effect.Error<G>, Effect.Effect.Context<G>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<G>>, Effect.Effect.Error<G>, Effect.Effect.Context<G>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -259,7 +239,7 @@ export declare namespace make {
e: (_: E, props: NoInfer<P>) => F, e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G, f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H, g: (_: G, props: NoInfer<P>) => H,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<H>>, Effect.Effect.Error<H>, Effect.Effect.Context<H>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<H>>, Effect.Effect.Error<H>, Effect.Effect.Context<H>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -277,7 +257,7 @@ export declare namespace make {
f: (_: F, props: NoInfer<P>) => G, f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H, g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I, h: (_: H, props: NoInfer<P>) => I,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<I>>, Effect.Effect.Error<I>, Effect.Effect.Context<I>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<I>>, Effect.Effect.Error<I>, Effect.Effect.Context<I>>
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I, J extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Utils.YieldWrap<Effect.Effect<any, any, any>>, A, B, C, D, E, F, G, H, I, J extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Generator<Eff, A, never>, body: (props: P) => Generator<Eff, A, never>,
a: ( a: (
@@ -296,35 +276,35 @@ export declare namespace make {
g: (_: G, props: NoInfer<P>) => H, g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I, h: (_: H, props: NoInfer<P>) => I,
i: (_: I, props: NoInfer<P>) => J, i: (_: I, props: NoInfer<P>) => J,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<J>>, Effect.Effect.Error<J>, Effect.Effect.Context<J>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<J>>, Effect.Effect.Error<J>, Effect.Effect.Context<J>>
} }
export type NonGen = { export type NonGen = {
<Eff extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, P extends {} = {}>(
body: (props: P) => Eff body: (props: P) => Eff
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => Eff, a: (_: A, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => Eff, b: (_: B, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => C, b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => Eff, c: (_: C, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
b: (_: B, props: NoInfer<P>) => C, b: (_: B, props: NoInfer<P>) => C,
c: (_: C, props: NoInfer<P>) => D, c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => Eff, d: (_: D, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
@@ -332,7 +312,7 @@ export declare namespace make {
c: (_: C, props: NoInfer<P>) => D, c: (_: C, props: NoInfer<P>) => D,
d: (_: D, props: NoInfer<P>) => E, d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => Eff, e: (_: E, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
@@ -341,7 +321,7 @@ export declare namespace make {
d: (_: D, props: NoInfer<P>) => E, d: (_: D, props: NoInfer<P>) => E,
e: (_: E, props: NoInfer<P>) => F, e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => Eff, f: (_: F, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
@@ -351,7 +331,7 @@ export declare namespace make {
e: (_: E, props: NoInfer<P>) => F, e: (_: E, props: NoInfer<P>) => F,
f: (_: F, props: NoInfer<P>) => G, f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => Eff, g: (_: G, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
@@ -362,7 +342,7 @@ export declare namespace make {
f: (_: F, props: NoInfer<P>) => G, f: (_: F, props: NoInfer<P>) => G,
g: (_: G, props: NoInfer<P>) => H, g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => Eff, h: (_: H, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
<Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, I, P extends {} = {}>( <Eff extends Effect.Effect<React.ReactNode, any, any>, A, B, C, D, E, F, G, H, I, P extends {} = {}>(
body: (props: P) => A, body: (props: P) => A,
a: (_: A, props: NoInfer<P>) => B, a: (_: A, props: NoInfer<P>) => B,
@@ -374,7 +354,7 @@ export declare namespace make {
g: (_: G, props: NoInfer<P>) => H, g: (_: G, props: NoInfer<P>) => H,
h: (_: H, props: NoInfer<P>) => I, h: (_: H, props: NoInfer<P>) => I,
i: (_: I, props: NoInfer<P>) => Eff, i: (_: I, props: NoInfer<P>) => Eff,
): Component.Default<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>> ): Component<P, Effect.Effect.Success<Effect.Effect.AsEffect<Eff>>, Effect.Effect.Error<Eff>, Effect.Effect.Context<Eff>>
} }
} }
@@ -520,24 +500,6 @@ export const makeUntraced: (
) )
) )
export declare namespace withSignature {
export type Result<T extends Component.Any, F extends Component.Signature> = (
& Omit<T, keyof Component.AsComponent<T>>
& Component<Component.Props<T>, Component.Success<T>, Component.Error<T>, Component.Context<T>, F>
)
}
export const withSignature: {
<F extends Component.Signature>(): <T extends Component.Any>(
self: T
) => withSignature.Result<T, F>
<F extends Component.Signature, T extends Component.Any>(
self: T
): withSignature.Result<T, F>
} = (self?: Component.Any): any => self === undefined
? identity
: self
/** /**
* Creates a new component with modified configuration options while preserving all original behavior. * Creates a new component with modified configuration options while preserving all original behavior.
* *
@@ -555,14 +517,14 @@ export const withSignature: {
* ``` * ```
*/ */
export const withOptions: { export const withOptions: {
<T extends Component.Any>( <T extends Component<any, any, any, any>>(
options: Partial<ComponentOptions> options: Partial<ComponentOptions>
): (self: T) => T ): (self: T) => T
<T extends Component.Any>( <T extends Component<any, any, any, any>>(
self: T, self: T,
options: Partial<ComponentOptions>, options: Partial<ComponentOptions>,
): T ): T
} = Function.dual(2, <T extends Component.Any>( } = Function.dual(2, <T extends Component<any, any, any, any>>(
self: T, self: T,
options: Partial<ComponentOptions>, options: Partial<ComponentOptions>,
): T => Object.setPrototypeOf( ): T => Object.setPrototypeOf(
@@ -608,19 +570,19 @@ export const withOptions: {
* *
*/ */
export const withRuntime: { export const withRuntime: {
<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( <P extends {}, A extends React.ReactNode, E, R>(
context: React.Context<Runtime.Runtime<R>>, context: React.Context<Runtime.Runtime<R>>,
): (self: Component<P, A, E, Scope.Scope | NoInfer<R>, F>) => F ): (self: Component<P, A, E, Scope.Scope | NoInfer<R>>) => (props: P) => A
<P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( <P extends {}, A extends React.ReactNode, E, R>(
self: Component<P, A, E, Scope.Scope | NoInfer<R>, F>, self: Component<P, A, E, Scope.Scope | NoInfer<R>>,
context: React.Context<Runtime.Runtime<R>>, context: React.Context<Runtime.Runtime<R>>,
): F ): (props: P) => A
} = Function.dual(2, <P extends {}, A extends React.ReactNode, E, R, F extends Component.Signature>( } = Function.dual(2, <P extends {}, A extends React.ReactNode, E, R>(
self: Component<P, A, E, R, F>, self: Component<P, A, E, R>,
context: React.Context<Runtime.Runtime<R>>, context: React.Context<Runtime.Runtime<R>>,
) => function WithRuntime(props: P) { ) => function WithRuntime(props: P) {
return React.createElement( return React.createElement(
Runtime.runSync(React.useContext(context))(self.use) as React.FC<P>, Runtime.runSync(React.useContext(context))(self.use),
props, props,
) )
}) })
+213 -16
View File
@@ -1,8 +1,11 @@
import { Array, type Cause, Chunk, type Duration, Effect, Equal, Function, identity, Option, type ParseResult, Pipeable, Predicate, type Scope, Stream, SubscriptionRef } 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 Lens from "./Lens.js"
import * as Mutation from "./Mutation.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"
export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form") export const FormTypeId: unique symbol = Symbol.for("@effect-fc/Form/Form")
@@ -17,8 +20,8 @@ extends Pipeable.Pipeable {
readonly encodedValue: Lens.Lens<I, ER, EW, never, never> readonly encodedValue: Lens.Lens<I, ER, EW, never, never>
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never> readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>
readonly isValidating: Subscribable.Subscribable<boolean, ER, never> readonly isValidating: Subscribable.Subscribable<boolean, ER, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never> readonly canSubmit: Subscribable.Subscribable<boolean, never, never>
readonly isCommitting: 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> export class FormImpl<out P extends readonly PropertyKey[], in out A, in out I = A, in out ER = never, in out EW = never>
@@ -31,15 +34,209 @@ extends Pipeable.Class() implements Form<P, A, I, ER, EW> {
readonly encodedValue: Lens.Lens<I, ER, EW, never, never>, readonly encodedValue: Lens.Lens<I, ER, EW, never, never>,
readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>, readonly issues: Subscribable.Subscribable<readonly ParseResult.ArrayFormatterIssue[], never, never>,
readonly isValidating: Subscribable.Subscribable<boolean, never, never>, readonly isValidating: Subscribable.Subscribable<boolean, never, never>,
readonly canCommit: Subscribable.Subscribable<boolean, never, never>, readonly canSubmit: Subscribable.Subscribable<boolean, never, never>,
readonly isCommitting: Subscribable.Subscribable<boolean, never, never>, readonly isSubmitting: Subscribable.Subscribable<boolean, never, never>,
) { ) {
super() 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 context: Context.Context<Scope.Scope | R>
readonly mutation: Mutation.Mutation<
readonly [value: A, form: RootForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP
>
readonly autosubmit: boolean
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never>
readonly run: Effect.Effect<void>
readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException>
}
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 RootForm<A, I, R, MA, ME, MR, MP> {
readonly [FormTypeId]: FormTypeId = FormTypeId
readonly [RootFormTypeId]: RootFormTypeId = RootFormTypeId
readonly path = [] as const
constructor(
readonly schema: Schema.Schema<A, I, R>,
readonly context: Context.Context<Scope.Scope | R>,
readonly mutation: Mutation.Mutation<
readonly [value: A, form: RootForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP
>,
readonly autosubmit: boolean,
readonly value: Lens.Lens<Option.Option<A>, never, never, never, never>,
readonly encodedValue: Lens.Lens<I, never, never, never, never>,
readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>,
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,
) {
super()
}
get run(): Effect.Effect<void> {
return this.runSemaphore.withPermits(1)(Stream.runForEach(
this.encodedValue.changes,
encodedValue => Lens.get(this.validationFiber).pipe(
Effect.andThen(Option.match({
onSome: Fiber.interrupt,
onNone: () => Effect.void,
})),
Effect.andThen(
Effect.forkScoped(Effect.onExit(
Schema.decode(this.schema, { errors: "all" })(encodedValue),
exit => Effect.andThen(
Exit.matchEffect(exit, {
onSuccess: v => Effect.andThen(
Lens.set(this.value, Option.some(v)),
Lens.set(this.issues, Array.empty()),
),
onFailure: c => Option.match(Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), {
onSome: e => Effect.flatMap(
ParseResult.ArrayFormatter.formatError(e),
v => Lens.set(this.issues, v),
),
onNone: () => Effect.void,
}),
}),
Lens.set(this.validationFiber, Option.none()),
),
)).pipe(
Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
Effect.andThen(Fiber.join),
Effect.andThen(value => this.autosubmit
? Effect.asVoid(Effect.forkScoped(this.submitValue(value)))
: Effect.void
),
Effect.forkScoped,
)
),
Effect.provide(this.context),
),
))
}
get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException> {
return Lens.get(this.value).pipe(
Effect.andThen(identity),
Effect.andThen(value => this.submitValue(value)),
)
}
submitValue(value: A): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>> {
return Effect.whenEffect(
Effect.tap(
this.mutation.mutate([value, this as any]),
result => Result.isFailure(result)
? Option.match(
Chunk.findFirst(
Cause.failures(result.cause as Cause.Cause<ParseResult.ParseError>),
e => e._tag === "ParseError",
),
{
onSome: e => Effect.flatMap(
ParseResult.ArrayFormatter.formatError(e),
v => Lens.set(this.issues, v),
),
onNone: () => Effect.void,
},
)
: Effect.void
),
this.canSubmit.get,
)
}
}
export const isForm = (u: unknown): u is Form<readonly PropertyKey[], 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 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<
readonly [value: NoInfer<A>, form: RootForm<NoInfer<A>, NoInfer<I>, NoInfer<R>, unknown, unknown, unknown>],
MA, ME, MR, MP
> {
readonly schema: Schema.Schema<A, I, R>
readonly initialEncodedValue: NoInfer<I>
readonly autosubmit?: boolean
}
}
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>
): Effect.fn.Return<
RootForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never,
Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> {
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,
yield* Effect.context<Scope.Scope | R>(),
mutation,
options.autosubmit ?? false,
valueLens,
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(options.initialEncodedValue)),
issuesLens,
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),
)
})
export declare namespace service {
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 make.Options<A, I, R, MA, ME, MR, MP> {}
}
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>
): Effect.Effect<
RootForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never,
Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> => Effect.tap(
make(options),
form => Effect.forkScoped(form.run),
)
const filterIssuesByPath = ( const filterIssuesByPath = (
@@ -49,7 +246,7 @@ const filterIssuesByPath = (
issue.path.length >= path.length && Array.every(path, (p, i) => p === issue.path[i]) issue.path.length >= path.length && Array.every(path, (p, i) => p === issue.path[i])
) )
export const focusObjectOn: { export const focusObjectField: {
<P extends readonly PropertyKey[], A extends object, I extends object, ER, EW, K extends keyof A & keyof I>( <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>, self: Form<P, A, I, ER, EW>,
key: K, key: K,
@@ -67,11 +264,11 @@ export const focusObjectOn: {
return new FormImpl( return new FormImpl(
path, path,
Subscribable.mapOption(form.value, a => a[key]), Subscribable.mapOption(form.value, a => a[key]),
Lens.focusObjectOn(form.encodedValue, key), Lens.focusObjectField(form.encodedValue, key),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canSubmit,
form.isCommitting, form.isSubmitting,
) )
}) })
@@ -96,8 +293,8 @@ export const focusArrayAt: {
Lens.focusArrayAt(form.encodedValue, index), Lens.focusArrayAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canSubmit,
form.isCommitting, form.isSubmitting,
) )
}) })
@@ -122,8 +319,8 @@ export const focusTupleAt: {
Lens.focusTupleAt(form.encodedValue, index), Lens.focusTupleAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canSubmit,
form.isCommitting, form.isSubmitting,
) )
}) })
@@ -148,8 +345,8 @@ export const focusChunkAt: {
Lens.focusChunkAt(form.encodedValue, index), Lens.focusChunkAt(form.encodedValue, index),
Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)), Subscribable.map(form.issues, issues => filterIssuesByPath(issues, path)),
form.isValidating, form.isValidating,
form.canCommit, form.canSubmit,
form.isCommitting, form.isSubmitting,
) )
}) })
@@ -192,7 +389,7 @@ export const useInput = Effect.fnUntraced(function* <P extends readonly Property
), ),
internalValue => Lens.set(form.encodedValue, internalValue), internalValue => Lens.set(form.encodedValue, internalValue),
), ),
], { concurrency: "unbounded", discard: true })) ], { concurrency: "unbounded" }))
return internalValueLens return internalValueLens
}), [form, options?.debounce]) }), [form, options?.debounce])
+5 -4
View File
@@ -1,8 +1,9 @@
import { Effect, Equivalence, Stream, SubscriptionRef } from "effect" import { Effect, Equivalence, Stream } from "effect"
import { Lens } from "effect-lens" import { Lens } 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"
import * as SetStateAction from "./SetStateAction.js" import * as SetStateAction from "./SetStateAction.js"
import * as SubscriptionRef from "./SubscriptionRef.js"
export * from "effect-lens/Lens" export * from "effect-lens/Lens"
@@ -37,15 +38,15 @@ export const useState = Effect.fnUntraced(function* <A, ER, EW, RR, RW>(
return [reactStateValue, setValue] return [reactStateValue, setValue]
}) })
export declare namespace useFromReactState { export declare namespace useFromState {
export interface Options<A> { export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A> readonly equivalence?: Equivalence.Equivalence<A>
} }
} }
export const useFromReactState = Effect.fnUntraced(function* <A>( export const useFromState = Effect.fnUntraced(function* <A>(
[value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>], [value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>],
options?: useFromReactState.Options<NoInfer<A>>, options?: useFromState.Options<NoInfer<A>>,
): Effect.fn.Return<Lens.Lens<A, never, never, never, never>> { ): Effect.fn.Return<Lens.Lens<A, never, never, never, never>> {
const lens = yield* Component.useOnMount(() => Effect.map( const lens = yield* Component.useOnMount(() => Effect.map(
SubscriptionRef.make(value), SubscriptionRef.make(value),
+4 -4
View File
@@ -61,7 +61,7 @@ export const isMemoized = (u: unknown): u is Memoized<unknown> => Predicate.hasP
* ) * )
* ``` * ```
*/ */
export const memoized = <T extends Component.Component.Any>( export const memoized = <T extends Component.Component<any, any, any, any>>(
self: T self: T
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf( ): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
Object.assign(function() {}, self), Object.assign(function() {}, self),
@@ -96,14 +96,14 @@ export const memoized = <T extends Component.Component.Any>(
* ``` * ```
*/ */
export const withOptions: { export const withOptions: {
<T extends Component.Component.Any & Memoized<any>>( <T extends Component.Component<any, any, any, any> & Memoized<any>>(
options: Partial<MemoizedOptions<Component.Component.Props<T>>> options: Partial<MemoizedOptions<Component.Component.Props<T>>>
): (self: T) => T ): (self: T) => T
<T extends Component.Component.Any & Memoized<any>>( <T extends Component.Component<any, any, any, any> & Memoized<any>>(
self: T, self: T,
options: Partial<MemoizedOptions<Component.Component.Props<T>>>, options: Partial<MemoizedOptions<Component.Component.Props<T>>>,
): T ): T
} = Function.dual(2, <T extends Component.Component.Any & Memoized<any>>( } = Function.dual(2, <T extends Component.Component<any, any, any, any> & Memoized<any>>(
self: T, self: T,
options: Partial<MemoizedOptions<Component.Component.Props<T>>>, options: Partial<MemoizedOptions<Component.Component.Props<T>>>,
): T => Object.setPrototypeOf( ): T => Object.setPrototypeOf(
-2
View File
@@ -99,10 +99,8 @@ extends Pipeable.Class() implements Mutation<K, A, E, R, P> {
} }
} }
export const isMutation = (u: unknown): u is Mutation<readonly unknown[], unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId) export const isMutation = (u: unknown): u is Mutation<readonly unknown[], unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId)
export declare namespace make { export declare namespace make {
export interface Options<K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never> { export interface Options<K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never> {
readonly f: (key: K) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<P>>> readonly f: (key: K) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<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]))
})
})
+98
View File
@@ -0,0 +1,98 @@
import { Array, Equivalence, Function, Option, Predicate } from "effect"
export type PropertyPath = readonly PropertyKey[]
type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
export type Paths<T, D extends number = 5, Seen = never> = readonly [] | (
D extends never ? readonly [] :
T extends Seen ? readonly [] :
T extends readonly any[] ? {
[K in keyof T as K extends number ? K : never]:
| readonly [K]
| readonly [K, ...Paths<T[K], Prev[D], Seen | T>]
} extends infer O
? O[keyof O]
: never
:
T extends object ? {
[K in keyof T as K extends string | number | symbol ? K : never]-?:
NonNullable<T[K]> extends infer V
? readonly [K] | readonly [K, ...Paths<V, Prev[D], Seen>]
: never
} extends infer O
? O[keyof O]
: never
:
never
)
export type ValueFromPath<T, P extends readonly any[]> = P extends readonly [infer Head, ...infer Tail]
? Head extends keyof T
? ValueFromPath<T[Head], Tail>
: T extends readonly any[]
? Head extends number
? ValueFromPath<T[number], Tail>
: never
: never
: T
export const equivalence: Equivalence.Equivalence<PropertyPath> = Equivalence.array(Equivalence.strict())
export const unsafeGet: {
<T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P>
<T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P>
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P> =>
path.reduce((acc: any, key: any) => acc?.[key], self)
)
export const get: {
<T, const P extends Paths<T>>(path: P): (self: T) => Option.Option<ValueFromPath<T, P>>
<T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>>
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>> =>
path.reduce(
(acc: Option.Option<any>, key: any): Option.Option<any> => Option.isSome(acc)
? Predicate.hasProperty(acc.value, key)
? Option.some(acc.value[key])
: Option.none()
: acc,
Option.some(self),
)
)
export const immutableSet: {
<T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => Option.Option<T>
<T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T>
} = Function.dual(3, <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> => {
const key = Array.head(path as PropertyPath)
if (Option.isNone(key))
return Option.some(value as T)
if (!Predicate.hasProperty(self, key.value))
return Option.none()
const child = immutableSet<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as PropertyPath)), value)
if (Option.isNone(child))
return child
if (Array.isArray(self))
return typeof key.value === "number"
? Option.some([
...self.slice(0, key.value),
child.value,
...self.slice(key.value + 1),
] as T)
: Option.none()
if (typeof self === "object")
return Option.some(
Object.assign(
Object.create(Object.getPrototypeOf(self)),
{ ...self, [key.value]: child.value },
)
)
return Option.none()
})
+1 -1
View File
@@ -3,7 +3,7 @@ import type * as React from "react"
import * as Component from "./Component.js" import * as Component from "./Component.js"
export const useFromReactiveValues = Effect.fnUntraced(function* <const A extends React.DependencyList>( export const usePubSubFromReactiveValues = Effect.fnUntraced(function* <const A extends React.DependencyList>(
values: A values: A
): Effect.fn.Return<PubSub.PubSub<A>, never, Scope.Scope> { ): Effect.fn.Return<PubSub.PubSub<A>, never, Scope.Scope> {
const pubsub = yield* Component.useOnMount(() => Effect.acquireRelease(PubSub.unbounded<A>(), PubSub.shutdown)) const pubsub = yield* Component.useOnMount(() => Effect.acquireRelease(PubSub.unbounded<A>(), PubSub.shutdown))
-2
View File
@@ -266,10 +266,8 @@ extends Pipeable.Class() implements Query<K, A, KE, KR, E, R, P> {
} }
} }
export const isQuery = (u: unknown): u is Query<readonly unknown[], unknown> => Predicate.hasProperty(u, QueryTypeId) export const isQuery = (u: unknown): u is Query<readonly unknown[], unknown> => Predicate.hasProperty(u, QueryTypeId)
export declare namespace make { export declare namespace make {
export interface Options<K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never> { export interface Options<K extends Query.AnyKey, A, KE = never, KR = never, E = never, R = never, P = never> {
readonly key: Stream.Stream<K, KE, KR> readonly key: Stream.Stream<K, KE, KR>
+11 -11
View File
@@ -1,4 +1,4 @@
import { Cause, Context, Data, Effect, Equal, Exit, type Fiber, Hash, Layer, Match, Pipeable, Predicate, PubSub, pipe, Ref, type Scope, Stream, type Subscribable, SynchronizedRef } 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" import { Lens } from "effect-lens"
@@ -212,24 +212,24 @@ export const unsafeForkEffect = Effect.fnUntraced(function* <A, E, R, P = never>
never, never,
Scope.Scope | unsafeForkEffect.OutputContext<R, P> Scope.Scope | unsafeForkEffect.OutputContext<R, P>
> { > {
const ref = (yield* SynchronizedRef.make( const ref = yield* SynchronizedRef.make<Result<A, E, P>>(options?.initial ?? initial<A, E, P>())
options?.initial ?? initial<A, E, P>()
)) as Lens.SynchronizedRefLensImpl.SynchronizedRefWithInternals<Result<A, E, P>>
const pubsub = yield* PubSub.unbounded<Result<A, E, P>>() const pubsub = yield* PubSub.unbounded<Result<A, E, P>>()
const state = Lens.make({ const state = Lens.make<Result<A, E, P>, never, never, never, never>({
get: Ref.get(ref.ref), get get() { return Ref.get(ref) },
get changes() { get changes() {
return Stream.unwrapScoped(Effect.map( return Stream.unwrapScoped(Effect.map(
Effect.all([Ref.get(ref.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),
)) ))
}, },
commit: value => Effect.zipLeft( modify: f => Ref.get(ref).pipe(
Ref.set(ref.ref, value), Effect.flatMap(f),
PubSub.publish(pubsub, value), Effect.flatMap(([b, a]) => Ref.set(ref, a).pipe(
Effect.as(b),
Effect.zipLeft(PubSub.publish(pubsub, a))
)),
), ),
lock: Effect.succeed(ref.withLock),
}) })
const fiber = yield* Effect.gen(function*() { const fiber = yield* Effect.gen(function*() {
+1 -1
View File
@@ -3,7 +3,7 @@ import * as React from "react"
import * as Component from "./Component.js" import * as Component from "./Component.js"
export const use: { export const useStream: {
<A, E, R>( <A, E, R>(
stream: Stream.Stream<A, E, R> stream: Stream.Stream<A, E, R>
): Effect.Effect<Option.Option<A>, never, R> ): Effect.Effect<Option.Option<A>, never, R>
-220
View File
@@ -1,220 +0,0 @@
import { Array, Cause, Chunk, type Context, Effect, Fiber, flow, identity, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, SubscriptionRef } from "effect"
import * as Form from "./Form.js"
import * as Lens from "./Lens.js"
import * as Mutation from "./Mutation.js"
import * as Result from "./Result.js"
import * as Subscribable from "./Subscribable.js"
export const SubmittableFormTypeId: unique symbol = Symbol.for("@effect-fc/Form/SubmittableForm")
export type SubmittableFormTypeId = typeof SubmittableFormTypeId
export interface SubmittableForm<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.Form<readonly [], A, I, never, never> {
readonly [SubmittableFormTypeId]: SubmittableFormTypeId
readonly schema: Schema.Schema<A, I, R>
readonly context: Context.Context<Scope.Scope | R>
readonly mutation: Mutation.Mutation<
readonly [value: A, form: SubmittableForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP
>
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never>
readonly run: Effect.Effect<void>
readonly submit: Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException>
}
export class SubmittableFormImpl<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 SubmittableForm<A, I, R, MA, ME, MR, MP> {
readonly [Form.FormTypeId]: Form.FormTypeId = Form.FormTypeId
readonly [SubmittableFormTypeId]: SubmittableFormTypeId = SubmittableFormTypeId
readonly path = [] as const
readonly encodedValue: Lens.Lens<I, never, never, never, never>
readonly isValidating: Subscribable.Subscribable<boolean, never, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never>
readonly isCommitting: Subscribable.Subscribable<boolean, never, never>
constructor(
readonly schema: Schema.Schema<A, I, R>,
readonly context: Context.Context<Scope.Scope | R>,
readonly mutation: Mutation.Mutation<
readonly [value: A, form: SubmittableForm<A, I, R, unknown, unknown, unknown>],
MA, ME, MR, MP
>,
readonly value: Lens.Lens<Option.Option<A>, never, never, never, never>,
readonly internalEncodedValue: Lens.Lens<I, never, never, never, never>,
readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>,
readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never, never, never>,
readonly runSemaphore: Effect.Semaphore,
) {
super()
this.encodedValue = Effect.all([
Effect.succeed(this),
Effect.succeed(Lens.asLensImpl(this.internalEncodedValue)),
]).pipe(
Effect.map(([self, parent]) => Lens.make({
get: parent.get,
get changes() { return parent.changes },
commit: a => Effect.andThen(
Effect.flatMap(
parent.resolve,
resolved => resolved.commit(Effect.succeed(a)),
),
self.synchronizeEncodedValue(a),
),
lock: parent.lock,
})),
Lens.unwrap,
)
this.isValidating = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.map(self.validationFiber, Option.isSome)),
Subscribable.unwrap,
)
this.canCommit = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.map(
Subscribable.zipLatestAll(self.value, self.issues, self.validationFiber, self.mutation.result),
([value, issues, validationFiber, result]) => (
Option.isSome(value) &&
Array.isEmptyReadonlyArray(issues) &&
Option.isNone(validationFiber) &&
!(Result.isRunning(result) || Result.hasRefreshingFlag(result))
),
)),
Subscribable.unwrap,
)
this.isCommitting = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.map(
self.mutation.result,
result => Result.isRunning(result) || Result.hasRefreshingFlag(result),
)),
Subscribable.unwrap,
)
}
synchronizeEncodedValue(encodedValue: I): Effect.Effect<void, never, never> {
return Lens.get(this.validationFiber).pipe(
Effect.andThen(Option.match({
onSome: Fiber.interrupt,
onNone: () => Effect.void,
})),
Effect.andThen(Effect.forkScoped(
Effect.ensuring(
Schema.decode(this.schema, { errors: "all" })(encodedValue),
Lens.set(this.validationFiber, Option.none()),
)
)),
Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
Effect.flatMap(Fiber.join),
Effect.tap(() => Lens.set(this.issues, Array.empty())),
Effect.flatMap(value => Lens.set(this.value, Option.some(value))),
Effect.catchIf(
ParseResult.isParseError,
flow(
ParseResult.ArrayFormatter.formatError,
Effect.flatMap(v => Lens.set(this.issues, v)),
),
),
Effect.provide(this.context),
)
}
get run(): Effect.Effect<void, never, never> {
return Lens.get(this.encodedValue).pipe(
Effect.flatMap(v => Schema.decode(this.schema)(v)),
Effect.option,
Effect.flatMap(v => Lens.set(this.value, v)),
Effect.provide(this.context),
this.runSemaphore.withPermits(1),
)
}
get submit(): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, Cause.NoSuchElementException, never> {
return Lens.get(this.value).pipe(
Effect.flatMap(identity),
Effect.flatMap(value => this.submitValue(value)),
)
}
submitValue(value: A): Effect.Effect<Option.Option<Result.Final<MA, ME, MP>>, never, never> {
return Effect.whenEffect(
Effect.tap(
this.mutation.mutate([value, this as any]),
result => Result.isFailure(result)
? Option.match(
Chunk.findFirst(
Cause.failures(result.cause as Cause.Cause<ParseResult.ParseError>),
e => e._tag === "ParseError",
),
{
onSome: e => Effect.flatMap(
ParseResult.ArrayFormatter.formatError(e),
v => Lens.set(this.issues, v),
),
onNone: () => Effect.void,
},
)
: Effect.void
),
this.canCommit.get,
)
}
}
export const isSubmittableForm = (u: unknown): u is SubmittableForm<unknown, unknown, unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, SubmittableFormTypeId)
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>
extends Mutation.make.Options<
readonly [value: NoInfer<A>, form: SubmittableForm<NoInfer<A>, NoInfer<I>, NoInfer<R>, unknown, unknown, unknown>],
MA, ME, MR, MP
> {
readonly schema: Schema.Schema<A, I, R>
readonly initialEncodedValue: NoInfer<I>
}
}
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>
): Effect.fn.Return<
SubmittableForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never,
Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> {
return new SubmittableFormImpl(
options.schema,
yield* Effect.context<Scope.Scope | R>(),
yield* Mutation.make(options),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<A>())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(options.initialEncodedValue)),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make<readonly ParseResult.ArrayFormatterIssue[]>(Array.empty())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>())),
yield* Effect.makeSemaphore(1),
)
})
export declare namespace service {
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 make.Options<A, I, R, MA, ME, MR, MP> {}
}
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>
): Effect.Effect<
SubmittableForm<A, I, R, MA, ME, Result.forkEffect.OutputContext<MR, MP>, MP>,
never,
Scope.Scope | R | Result.forkEffect.OutputContext<MR, MP>
> => Effect.tap(
make(options),
form => Effect.forkScoped(form.run),
)
+4 -4
View File
@@ -19,7 +19,7 @@ export const zipLatestAll = <const T extends readonly Subscribable.Subscribable<
changes: Stream.zipLatestAll(...elements.map(v => v.changes)), changes: Stream.zipLatestAll(...elements.map(v => v.changes)),
}) as any }) as any
export declare namespace useAll { export declare namespace useSubscribables {
export type Success<T extends readonly Subscribable.Subscribable<any, any, any>[]> = [T[number]] extends [never] export type Success<T extends readonly Subscribable.Subscribable<any, any, any>[]> = [T[number]] extends [never]
? never ? never
: { [K in keyof T]: T[K] extends Subscribable.Subscribable<infer A, infer _E, infer _R> ? A : never } : { [K in keyof T]: T[K] extends Subscribable.Subscribable<infer A, infer _E, infer _R> ? A : never }
@@ -29,11 +29,11 @@ export declare namespace useAll {
} }
} }
export const useAll = Effect.fnUntraced(function* <const T extends readonly Subscribable.Subscribable<any, any, any>[]>( export const useSubscribables = Effect.fnUntraced(function* <const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
elements: T, elements: T,
options?: useAll.Options<useAll.Success<NoInfer<T>>>, options?: useSubscribables.Options<useSubscribables.Success<NoInfer<T>>>,
): Effect.fn.Return< ): Effect.fn.Return<
useAll.Success<T>, useSubscribables.Success<T>,
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer E, infer _R> ? E : never, [T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer E, infer _R> ? E : never,
[T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer _E, infer R> ? R : never [T[number]] extends [never] ? never : T[number] extends Subscribable.Subscribable<infer _A, infer _E, infer R> ? R : never
> { > {
+61
View File
@@ -0,0 +1,61 @@
import { Effect, Equivalence, Ref, Stream, SubscriptionRef } from "effect"
import * as React from "react"
import * as Component from "./Component.js"
import * as SetStateAction from "./SetStateAction.js"
export declare namespace useSubscriptionRefState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useSubscriptionRefState = Effect.fnUntraced(function* <A>(
ref: SubscriptionRef.SubscriptionRef<A>,
options?: useSubscriptionRefState.Options<NoInfer<A>>,
): Effect.fn.Return<readonly [A, React.Dispatch<React.SetStateAction<A>>]> {
const [reactStateValue, setReactStateValue] = React.useState(yield* Component.useOnMount(() => ref))
yield* Component.useReactEffect(() => Effect.forkScoped(
Stream.runForEach(
Stream.changesWith(ref.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setReactStateValue(v)),
)
), [ref])
const setValue = yield* Component.useCallbackSync(
(setStateAction: React.SetStateAction<A>) => Effect.andThen(
Ref.updateAndGet(ref, prevState => SetStateAction.value(setStateAction, prevState)),
v => setReactStateValue(v),
),
[ref],
)
return [reactStateValue, setValue]
})
export declare namespace useSubscriptionRefFromState {
export interface Options<A> {
readonly equivalence?: Equivalence.Equivalence<A>
}
}
export const useSubscriptionRefFromState = Effect.fnUntraced(function* <A>(
[value, setValue]: readonly [A, React.Dispatch<React.SetStateAction<A>>],
options?: useSubscriptionRefFromState.Options<NoInfer<A>>,
): Effect.fn.Return<SubscriptionRef.SubscriptionRef<A>> {
const ref = yield* Component.useOnChange(() => Effect.tap(
SubscriptionRef.make(value),
ref => Effect.forkScoped(
Stream.runForEach(
Stream.changesWith(ref.changes, options?.equivalence ?? Equivalence.strict()),
v => Effect.sync(() => setValue(v)),
)
),
), [setValue])
yield* Component.useReactEffect(() => Ref.set(ref, value), [value])
return ref
})
export * from "effect/SubscriptionRef"
@@ -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
})
})
@@ -0,0 +1,186 @@
import { Chunk, Effect, Effectable, Option, Predicate, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types, type Unify } from "effect"
import * as PropertyPath from "./PropertyPath.js"
export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("@effect-fc/SubscriptionSubRef/SubscriptionSubRef")
export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId
export interface SubscriptionSubRef<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
extends SubscriptionSubRef.Variance<A, B>, SubscriptionRef.SubscriptionRef<A> {
readonly parent: B
readonly [Unify.typeSymbol]?: unknown
readonly [Unify.unifySymbol]?: SubscriptionSubRefUnify<this>
readonly [Unify.ignoreSymbol]?: SubscriptionSubRefUnifyIgnore
}
export declare namespace SubscriptionSubRef {
export interface Variance<in out A, in out B> {
readonly [SubscriptionSubRefTypeId]: {
readonly _A: Types.Invariant<A>
readonly _B: Types.Invariant<B>
}
}
}
export interface SubscriptionSubRefUnify<A extends { [Unify.typeSymbol]?: any }> extends SubscriptionRef.SubscriptionRefUnify<A> {
SubscriptionSubRef?: () => Extract<A[Unify.typeSymbol], SubscriptionSubRef<any, any>>
}
export interface SubscriptionSubRefUnifyIgnore extends SubscriptionRef.SubscriptionRefUnifyIgnore {
SubscriptionRef?: true
}
const refVariance = { _A: (_: any) => _ }
const synchronizedRefVariance = { _A: (_: any) => _ }
const subscriptionRefVariance = { _A: (_: any) => _ }
const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ }
class SubscriptionSubRefImpl<in out A, in out B extends SubscriptionRef.SubscriptionRef<any>>
extends Effectable.Class<A> implements SubscriptionSubRef<A, B> {
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
readonly [Ref.RefTypeId] = refVariance
readonly [SynchronizedRef.SynchronizedRefTypeId] = synchronizedRefVariance
readonly [SubscriptionRef.SubscriptionRefTypeId] = subscriptionRefVariance
readonly [SubscriptionSubRefTypeId] = subscriptionSubRefVariance
readonly get: Effect.Effect<A>
constructor(
readonly parent: B,
readonly getter: (parentValue: Effect.Effect.Success<B>) => A,
readonly setter: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>,
) {
super()
this.get = Effect.map(this.parent, this.getter)
}
commit() {
return this.get
}
get changes(): Stream.Stream<A> {
return Stream.unwrap(
Effect.map(this.get, a => Stream.concat(
Stream.make(a),
Stream.map(this.parent.changes, this.getter),
))
)
}
modify<C>(f: (a: A) => readonly [C, A]): Effect.Effect<C> {
return this.modifyEffect(a => Effect.succeed(f(a)))
}
modifyEffect<C, E, R>(f: (a: A) => Effect.Effect<readonly [C, A], E, R>): Effect.Effect<C, E, R> {
return Effect.Do.pipe(
Effect.bind("b", (): Effect.Effect<Effect.Effect.Success<B>> => this.parent),
Effect.bind("ca", ({ b }) => f(this.getter(b))),
Effect.tap(({ b, ca: [, a] }) => SubscriptionRef.set(this.parent, this.setter(b, a))),
Effect.map(({ ca: [c] }) => c),
)
}
}
export const isSubscriptionSubRef = (u: unknown): u is SubscriptionSubRef<unknown, SubscriptionRef.SubscriptionRef<unknown>> => Predicate.hasProperty(u, SubscriptionSubRefTypeId)
export const makeFromGetSet = <A, B extends SubscriptionRef.SubscriptionRef<any>>(
parent: B,
options: {
readonly get: (parentValue: Effect.Effect.Success<B>) => A
readonly set: (parentValue: Effect.Effect.Success<B>, value: A) => Effect.Effect.Success<B>
},
): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, options.get, options.set)
export const makeFromPath = <
B extends SubscriptionRef.SubscriptionRef<any>,
const P extends PropertyPath.Paths<Effect.Effect.Success<B>>,
>(
parent: B,
path: P,
): SubscriptionSubRef<PropertyPath.ValueFromPath<Effect.Effect.Success<B>, P>, B> => new SubscriptionSubRefImpl(
parent,
parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)),
(parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)),
)
export const makeFromChunkIndex: {
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
parent: B,
index: number,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
B
>
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
parent: B,
index: number,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
B
>
} = (
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>,
index: number,
) => new SubscriptionSubRefImpl(
parent,
parentValue => Chunk.unsafeGet(parentValue, index),
(parentValue, value) => Chunk.replace(parentValue, index, value),
) as any
export const makeFromChunkFindFirst: {
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
parent: B,
findFirstPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never>,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
B
>
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
parent: B,
findFirstPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never>,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
B
>
} = (
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<never>>,
findFirstPredicate: Predicate.Predicate.Any,
) => new SubscriptionSubRefImpl(
parent,
parentValue => Option.getOrThrow(Chunk.findFirst(parentValue, findFirstPredicate)),
(parentValue, value) => Option.getOrThrow(Option.andThen(
Chunk.findFirstIndex(parentValue, findFirstPredicate),
index => Chunk.replace(parentValue, index, value),
)),
) as any
export const makeFromChunkFindLast: {
<B extends SubscriptionRef.SubscriptionRef<Chunk.NonEmptyChunk<any>>>(
parent: B,
findLastPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never>,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.NonEmptyChunk<infer A> ? A : never,
B
>
<B extends SubscriptionRef.SubscriptionRef<Chunk.Chunk<any>>>(
parent: B,
findLastPredicate: Predicate.Predicate<Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never>,
): SubscriptionSubRef<
Effect.Effect.Success<B> extends Chunk.Chunk<infer A> ? A : never,
B
>
} = (
parent: SubscriptionRef.SubscriptionRef<Chunk.Chunk<never>>,
findLastPredicate: Predicate.Predicate.Any,
) => new SubscriptionSubRefImpl(
parent,
parentValue => Option.getOrThrow(Chunk.findLast(parentValue, findLastPredicate)),
(parentValue, value) => Option.getOrThrow(Option.andThen(
Chunk.findLastIndex(parentValue, findLastPredicate),
index => Chunk.replace(parentValue, index, value),
)),
) as any
-223
View File
@@ -1,223 +0,0 @@
import { Array, type Context, Effect, Equal, Fiber, flow, Option, ParseResult, Pipeable, Predicate, Schema, type Scope, Stream, SubscriptionRef } from "effect"
import * as Form from "./Form.js"
import * as Lens from "./Lens.js"
import * as Subscribable from "./Subscribable.js"
export const SynchronizedFormTypeId: unique symbol = Symbol.for("@effect-fc/Form/SynchronizedForm")
export type SynchronizedFormTypeId = typeof SynchronizedFormTypeId
export interface SynchronizedForm<
in out A,
in out I = A,
in out R = never,
in out TER = never,
in out TEW = never,
in out TRR = never,
in out TRW = never,
> extends Form.Form<readonly [], A, I, TER, TER | TEW> {
readonly [SynchronizedFormTypeId]: SynchronizedFormTypeId
readonly schema: Schema.Schema<A, I, R>
readonly context: Context.Context<Scope.Scope | R | TRR | TRW>
readonly target: Lens.Lens<A, TER, TEW, TRR, TRW>
readonly validationFiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never>
readonly run: Effect.Effect<void, TER>
}
export class SynchronizedFormImpl<
in out A,
in out I = A,
in out R = never,
in out TER = never,
in out TEW = never,
in out TRR = never,
in out TRW = never,
> extends Pipeable.Class() implements SynchronizedForm<A, I, R, TER, TEW, TRR, TRW> {
readonly [Form.FormTypeId]: Form.FormTypeId = Form.FormTypeId
readonly [SynchronizedFormTypeId]: SynchronizedFormTypeId = SynchronizedFormTypeId
readonly path = [] as const
readonly value: Subscribable.Subscribable<Option.Option<A>, never, never>
readonly encodedValue: Lens.Lens<I, TER, TER | TEW, never, never>
readonly isValidating: Subscribable.Subscribable<boolean, never, never>
readonly canCommit: Subscribable.Subscribable<boolean, never, never>
constructor(
readonly schema: Schema.Schema<A, I, R>,
readonly context: Context.Context<Scope.Scope | R | TRR | TRW>,
readonly target: Lens.Lens<A, TER, TEW, TRR, TRW>,
readonly internalEncodedValue: Lens.Lens<I, never, never, never, never>,
readonly issues: Lens.Lens<readonly ParseResult.ArrayFormatterIssue[], never, never, never, never>,
readonly validationFiber: Lens.Lens<Option.Option<Fiber.Fiber<A, ParseResult.ParseError>>, never, never, never, never>,
readonly isCommitting: Lens.Lens<boolean, never, never>,
readonly runSemaphore: Effect.Semaphore,
) {
super()
this.value = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.make({
get: Effect.provide(Effect.option(self.target.get), self.context),
get changes() {
return Stream.provideContext(
self.target.changes.pipe(
Stream.map(Option.some),
Stream.catchAll(() => Stream.make(Option.none())),
),
self.context,
)
},
})),
Subscribable.unwrap,
)
this.encodedValue = Effect.all([
Effect.succeed(this),
Effect.succeed(Lens.asLensImpl(this.internalEncodedValue)),
]).pipe(
Effect.map(([self, parent]) => Lens.make<I, TER, TER | TEW, never, never>({
get: parent.get,
get changes() { return parent.changes },
commit: a => Effect.andThen(
Effect.flatMap(
parent.resolve,
resolved => resolved.commit(Effect.succeed(a)),
),
self.synchronizeEncodedValue(a),
),
lock: parent.lock,
})),
Lens.unwrap,
)
this.isValidating = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.map(self.validationFiber, Option.isSome)),
Subscribable.unwrap,
)
this.canCommit = Effect.succeed(this).pipe(
Effect.map(self => Subscribable.map(
Subscribable.zipLatestAll(self.issues, self.validationFiber, self.isCommitting),
([issues, validationFiber, isCommitting]) => (
Array.isEmptyReadonlyArray(issues) &&
Option.isNone(validationFiber) &&
!isCommitting
),
)),
Subscribable.unwrap,
)
}
synchronizeEncodedValue(encodedValue: I): Effect.Effect<void, TER | TEW, never> {
return Lens.get(this.validationFiber).pipe(
Effect.andThen(Option.match({
onSome: Fiber.interrupt,
onNone: () => Effect.void,
})),
Effect.andThen(Effect.forkScoped(
Effect.ensuring(
Schema.decode(this.schema, { errors: "all" })(encodedValue),
Lens.set(this.validationFiber, Option.none()),
)
)),
Effect.tap(fiber => Lens.set(this.validationFiber, Option.some(fiber))),
Effect.flatMap(Fiber.join),
Effect.flatMap(value => Effect.ensuring(
Lens.set(this.isCommitting, true).pipe(
Effect.andThen(Lens.set(this.issues, Array.empty())),
Effect.andThen(Lens.set(this.target, value)),
),
Lens.set(this.isCommitting, false),
)),
Effect.catchIf(
ParseResult.isParseError,
flow(
ParseResult.ArrayFormatter.formatError,
Effect.flatMap(v => Lens.set(this.issues, v)),
),
),
Effect.provide(this.context),
)
}
get run(): Effect.Effect<void, TER, never> {
return this.runSemaphore.withPermits(1)(Effect.provide(
Stream.runForEach(
Stream.drop(this.target.changes, 1),
targetValue => Schema.encode(this.schema, { errors: "all" })(targetValue).pipe(
Effect.flatMap(encodedValue => Effect.whenEffect(
Effect.andThen(
Lens.set(this.issues, Array.empty()),
Lens.set(this.internalEncodedValue, encodedValue),
),
Effect.map(
Lens.get(this.internalEncodedValue),
currentEncodedValue => !Equal.equals(encodedValue, currentEncodedValue),
),
)),
Effect.ignore,
),
),
this.context,
))
}
}
export const isSynchronizedForm = (u: unknown): u is SynchronizedForm<unknown, unknown, unknown, unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, SynchronizedFormTypeId)
export declare namespace make {
export interface Options<in out A, in out I = A, in out R = never, in out TER = never, in out TEW = never, in out TRR = never, in out TRW = never> {
readonly schema: Schema.Schema<A, I, R>
readonly target: Lens.Lens<A, TER, TEW, TRR, TRW>
readonly initialEncodedValue?: NoInfer<I>
}
}
export const make = Effect.fnUntraced(function* <A, I = A, R = never, TER = never, TEW = never, TRR = never, TRW = never>(
options: make.Options<A, I, R, TER, TEW, TRR, TRW>
): Effect.fn.Return<
SynchronizedForm<A, I, R, TER, TEW, TRR, TRW>,
ParseResult.ParseError | TER,
Scope.Scope | R | TRR | TRW
> {
const initialEncodedValue = options.initialEncodedValue !== undefined
? options.initialEncodedValue
: yield* Effect.flatMap(
Lens.get(options.target),
Schema.encode(options.schema, { errors: "all" }),
)
return new SynchronizedFormImpl(
options.schema,
yield* Effect.context<Scope.Scope | R | TRR | TRW>(),
options.target,
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(initialEncodedValue)),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make<readonly ParseResult.ArrayFormatterIssue[]>(Array.empty())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, ParseResult.ParseError>>())),
Lens.fromSubscriptionRef(yield* SubscriptionRef.make(false)),
yield* Effect.makeSemaphore(1),
)
})
export declare namespace service {
export interface Options<in out A, in out I = A, in out R = never, in out TER = never, in out TEW = never, in out TRR = never, in out TRW = never>
extends make.Options<A, I, R, TER, TEW, TRR, TRW> {}
}
export const service = <A, I = A, R = never, TER = never, TEW = never, TRR = never, TRW = never>(
options: service.Options<A, I, R, TER, TEW, TRR, TRW>
): Effect.Effect<
SynchronizedForm<A, I, R, TER, TEW, TRR, TRW>,
ParseResult.ParseError | TER,
Scope.Scope | R | TRR | TRW
> => Effect.tap(
make(options),
form => Effect.forkScoped(form.run),
)
+3 -2
View File
@@ -5,6 +5,7 @@ export * as Form from "./Form.js"
export * as Lens from "./Lens.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 PubSub from "./PubSub.js" export * as PubSub from "./PubSub.js"
export * as Query from "./Query.js" export * as Query from "./Query.js"
export * as QueryClient from "./QueryClient.js" export * as QueryClient from "./QueryClient.js"
@@ -12,6 +13,6 @@ export * as ReactRuntime from "./ReactRuntime.js"
export * as Result from "./Result.js" export * as Result from "./Result.js"
export * as SetStateAction from "./SetStateAction.js" export * as SetStateAction from "./SetStateAction.js"
export * as Stream from "./Stream.js" export * as Stream from "./Stream.js"
export * as SubmittableForm from "./SubmittableForm.js"
export * as Subscribable from "./Subscribable.js" export * as Subscribable from "./Subscribable.js"
export * as SynchronizedForm from "./SynchronizedForm.js" export * as SubscriptionRef from "./SubscriptionRef.js"
export * as SubscriptionSubRef from "./SubscriptionSubRef.js"
+15 -15
View File
@@ -13,30 +13,30 @@
"clean:modules": "rm -rf node_modules" "clean:modules": "rm -rf node_modules"
}, },
"devDependencies": { "devDependencies": {
"@tanstack/react-router": "^1.170.10", "@tanstack/react-router": "^1.168.3",
"@tanstack/react-router-devtools": "^1.167.0", "@tanstack/react-router-devtools": "^1.166.11",
"@tanstack/router-plugin": "^1.168.13", "@tanstack/router-plugin": "^1.167.4",
"@types/react": "^19.2.15", "@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2", "@vitejs/plugin-react": "^6.0.1",
"globals": "^17.6.0", "globals": "^17.4.0",
"react": "^19.2.6", "react": "^19.2.4",
"react-dom": "^19.2.6", "react-dom": "^19.2.4",
"type-fest": "^5.7.0", "type-fest": "^5.5.0",
"vite": "^8.0.16" "vite": "^8.0.2"
}, },
"dependencies": { "dependencies": {
"@effect/platform": "^0.96.1", "@effect/platform": "^0.96.0",
"@effect/platform-browser": "^0.76.0", "@effect/platform-browser": "^0.76.0",
"@radix-ui/themes": "^3.3.0", "@radix-ui/themes": "^3.3.0",
"@typed/id": "^0.17.2", "@typed/id": "^0.17.2",
"effect": "^3.21.2", "effect": "^3.21.0",
"effect-fc": "workspace:*", "effect-fc": "workspace:*",
"react-icons": "^5.6.0" "react-icons": "^5.6.0"
}, },
"overrides": { "overrides": {
"@types/react": "^19.2.15", "@types/react": "^19.2.14",
"effect": "^3.21.2", "effect": "^3.21.0",
"react": "^19.2.6" "react": "^19.2.4"
} }
} }
@@ -1,26 +1,22 @@
import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes" import { Callout, Flex, Spinner, TextField } from "@radix-ui/themes"
import { Array, Option, Struct } from "effect" import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
import type * as React from "react"
export declare namespace TextFieldFormInputView { export declare namespace TextFieldFormInputView {
export interface Props<out P extends readonly PropertyKey[], A, ER, EW> export interface Props extends Omit<TextField.RootProps, "form">, Form.useInput.Options {
extends Omit<TextField.RootProps, "form">, Form.useInput.Options { readonly form: Form.Form<readonly PropertyKey[], any, string>
readonly form: Form.Form<P, A, string, ER, EW> }
} }
export type Signature = <P extends readonly PropertyKey[], A, ER, EW>(props: Props<P, A, ER, EW>) => React.ReactNode export class TextFieldFormInputView extends Component.make("TextFieldFormInputView")(function*(
} props: TextFieldFormInputView.Props
export const TextFieldFormInputView = Component.make("TextFieldFormInputView")(function*(
props: TextFieldFormInputView.Props<readonly PropertyKey[], any, any, any>
) { ) {
const input = yield* Form.useInput(props.form, props) const input = yield* Form.useInput(props.form, props)
const [issues, isValidating, isCommitting] = yield* Subscribable.useAll([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.form.issues, props.form.issues,
props.form.isValidating, props.form.isValidating,
props.form.isCommitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -28,7 +24,7 @@ export const TextFieldFormInputView = Component.make("TextFieldFormInputView")(f
<TextField.Root <TextField.Root
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={isCommitting} disabled={isSubmitting}
{...Struct.omit(props, "form")} {...Struct.omit(props, "form")}
> >
{isValidating && {isValidating &&
@@ -51,6 +47,4 @@ export const TextFieldFormInputView = Component.make("TextFieldFormInputView")(f
})} })}
</Flex> </Flex>
) )
}).pipe( }) {}
Component.withSignature<TextFieldFormInputView.Signature>()
)
@@ -1,26 +1,22 @@
import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes" import { Callout, Flex, Spinner, Switch, TextField } from "@radix-ui/themes"
import { Array, Option, Struct } from "effect" import { Array, Option, Struct } from "effect"
import { Component, Form, Subscribable } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
import type * as React from "react"
export declare namespace TextFieldOptionalFormInputView { export declare namespace TextFieldOptionalFormInputView {
export interface Props<out P extends readonly PropertyKey[], A, ER, EW> export interface Props extends Omit<TextField.RootProps, "form" | "defaultValue">, Form.useOptionalInput.Options<string> {
extends Omit<TextField.RootProps, "form" | "defaultValue">, Form.useOptionalInput.Options<string> { readonly form: Form.Form<readonly PropertyKey[], any, Option.Option<string>>
readonly form: Form.Form<P, A, Option.Option<string>, ER, EW> }
} }
export type Signature = <P extends readonly PropertyKey[], A, ER, EW>(props: Props<P, A, ER, EW>) => React.ReactNode export class TextFieldOptionalFormInputView extends Component.make("TextFieldOptionalFormInputView")(function*(
} props: TextFieldOptionalFormInputView.Props
export const TextFieldOptionalFormInputView = Component.make("TextFieldOptionalFormInputView")(function*(
props: TextFieldOptionalFormInputView.Props<readonly PropertyKey[], any, any, any>
) { ) {
const input = yield* Form.useOptionalInput(props.form, props) const input = yield* Form.useOptionalInput(props.form, props)
const [issues, isValidating, isCommitting] = yield* Subscribable.useAll([ const [issues, isValidating, isSubmitting] = yield* Subscribable.useSubscribables([
props.form.issues, props.form.issues,
props.form.isValidating, props.form.isValidating,
props.form.isCommitting, props.form.isSubmitting,
]) ])
return ( return (
@@ -28,7 +24,7 @@ export const TextFieldOptionalFormInputView = Component.make("TextFieldOptionalF
<TextField.Root <TextField.Root
value={input.value} value={input.value}
onChange={e => input.setValue(e.target.value)} onChange={e => input.setValue(e.target.value)}
disabled={!input.enabled || isCommitting} disabled={!input.enabled || isSubmitting}
{...Struct.omit(props, "form", "defaultValue")} {...Struct.omit(props, "form", "defaultValue")}
> >
<TextField.Slot side="left"> <TextField.Slot side="left">
@@ -59,6 +55,4 @@ export const TextFieldOptionalFormInputView = Component.make("TextFieldOptionalF
})} })}
</Flex> </Flex>
) )
}).pipe( }) {}
Component.withSignature<TextFieldOptionalFormInputView.Signature>()
)
+11 -11
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, SubmittableForm, 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>(
@@ -41,7 +41,7 @@ const RegisterFormSubmitSchema = Schema.Struct({
class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", { class RegisterFormService extends Effect.Service<RegisterFormService>()("RegisterFormService", {
scoped: Effect.gen(function*() { scoped: Effect.gen(function*() {
const form = yield* SubmittableForm.service({ const form = yield* Form.service({
schema: RegisterFormSchema.pipe( schema: RegisterFormSchema.pipe(
Schema.compose( Schema.compose(
Schema.transformOrFail( Schema.transformOrFail(
@@ -64,17 +64,17 @@ class RegisterFormService extends Effect.Service<RegisterFormService>()("Registe
return { return {
form, form,
emailField: Form.focusObjectOn(form, "email"), emailField: Form.focusObjectField(form, "email"),
passwordField: Form.focusObjectOn(form, "password"), passwordField: Form.focusObjectField(form, "password"),
birthField: Form.focusObjectOn(form, "birth"), birthField: Form.focusObjectField(form, "birth"),
} as const } 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 [canCommit, submitResult] = yield* Subscribable.useAll([ const [canSubmit, submitResult] = yield* Subscribable.useSubscribables([
form.form.canCommit, form.form.canSubmit,
form.form.mutation.result, form.form.mutation.result,
]) ])
@@ -111,7 +111,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
defaultValue="" defaultValue=""
/> />
<Button disabled={!canCommit}>Submit</Button> <Button disabled={!canSubmit}>Submit</Button>
</Flex> </Flex>
</form> </form>
+9 -9
View File
@@ -1,8 +1,8 @@
import { HttpClient, type HttpClientError } from "@effect/platform" import { HttpClient, type HttpClientError } from "@effect/platform"
import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes" import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Array, Cause, Chunk, Console, Effect, flow, Match, Option, Schema, Stream, SubscriptionRef } from "effect" import { Array, Cause, Chunk, Console, Effect, flow, Match, Option, Schema, Stream } from "effect"
import { Component, ErrorObserver, Lens, Mutation, Query, Result, Subscribable } from "effect-fc" import { Component, ErrorObserver, Mutation, Query, Result, Subscribable, SubscriptionRef } from "effect-fc"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
@@ -16,9 +16,9 @@ const Post = Schema.Struct({
const ResultView = Component.make("ResultView")(function*() { const ResultView = Component.make("ResultView")(function*() {
const runPromise = yield* Component.useRunPromise() const runPromise = yield* Component.useRunPromise()
const [idLens, query, mutation] = yield* Component.useOnMount(() => Effect.gen(function*() { const [idRef, query, mutation] = yield* Component.useOnMount(() => Effect.gen(function*() {
const idLens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(1)) const idRef = yield* SubscriptionRef.make(1)
const key = Stream.map(idLens.changes, id => [id] as const) const key = Stream.map(idRef.changes, id => [id] as const)
const query = yield* Query.service({ const query = yield* Query.service({
key, key,
@@ -40,11 +40,11 @@ const ResultView = Component.make("ResultView")(function*() {
), ),
}) })
return [idLens, query, mutation] as const return [idRef, query, mutation] as const
})) }))
const [id, setId] = yield* Lens.useState(idLens) const [id, setId] = yield* SubscriptionRef.useSubscriptionRefState(idRef)
const [queryResult, mutationResult] = yield* Subscribable.useAll([query.result, mutation.result]) const [queryResult, mutationResult] = yield* Subscribable.useSubscribables([query.result, mutation.result])
yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe( yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe(
Effect.andThen(observer => observer.subscribe), Effect.andThen(observer => observer.subscribe),
@@ -105,7 +105,7 @@ const ResultView = Component.make("ResultView")(function*() {
</div> </div>
<Flex direction="row" justify="center" align="center" gap="1"> <Flex direction="row" justify="center" align="center" gap="1">
<Button onClick={() => runPromise(Effect.andThen(Lens.get(idLens), id => mutation.mutate([id])))}>Mutate</Button> <Button onClick={() => runPromise(Effect.andThen(idRef, id => mutation.mutate([id])))}>Mutate</Button>
</Flex> </Flex>
</Flex> </Flex>
</Container> </Container>
+1 -1
View File
@@ -21,7 +21,7 @@ const ResultView = Component.makeUntraced("Result")(function*() {
Effect.tap(Effect.sleep("250 millis")), Effect.tap(Effect.sleep("250 millis")),
Result.forkEffect, Result.forkEffect,
)) ))
const [result] = yield* Subscribable.useAll([resultSubscribable]) const [result] = yield* Subscribable.useSubscribables([resultSubscribable])
yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe( yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe(
Effect.andThen(observer => observer.subscribe), Effect.andThen(observer => observer.subscribe),
@@ -1,88 +0,0 @@
import { Box, Flex, IconButton } from "@radix-ui/themes"
import { Effect } from "effect"
import { Component, Form, Subscribable, SynchronizedForm } from "effect-fc"
import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { TodoFormSchema } from "./TodoFormSchema"
import { TodosState } from "./TodosState"
export interface EditTodoViewProps {
readonly id: string
}
export class EditTodoView extends Component.make("TodoView")(function*(props: EditTodoViewProps) {
const state = yield* TodosState
const [
indexSubscribable,
contentField,
completedAtField,
] = yield* Component.useOnChange(() => Effect.gen(function*() {
const indexSubscribable = state.getIndexSubscribable(props.id)
const form = yield* SynchronizedForm.service({
schema: TodoFormSchema,
target: state.getElementLens(props.id),
})
return [
indexSubscribable,
Form.focusObjectOn(form, "content"),
Form.focusObjectOn(form, "completedAt"),
] as const
}), [props.id])
const [index, size] = yield* Subscribable.useAll([
indexSubscribable,
state.sizeSubscribable,
])
const runSync = yield* Component.useRunSync()
const TextFieldFormInput = yield* TextFieldFormInputView.use
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
return (
<Flex direction="row" align="center" gap="2">
<Box flexGrow="1">
<Flex direction="column" align="stretch" gap="2">
<TextFieldFormInput
form={contentField}
debounce="250 millis"
/>
<Flex direction="row" justify="center" align="center" gap="2">
<TextFieldOptionalFormInput
form={completedAtField}
type="datetime-local"
defaultValue=""
/>
</Flex>
</Flex>
</Box>
<Flex direction="column" justify="center" align="center" gap="1">
<IconButton
disabled={index <= 0}
onClick={() => runSync(state.moveLeft(props.id))}
>
<FaArrowUp />
</IconButton>
<IconButton
disabled={index >= size - 1}
onClick={() => runSync(state.moveRight(props.id))}
>
<FaArrowDown />
</IconButton>
<IconButton onClick={() => runSync(state.remove(props.id))}>
<FaDeleteLeft />
</IconButton>
</Flex>
</Flex>
)
}) {}
-78
View File
@@ -1,78 +0,0 @@
import { Box, Button, Flex } from "@radix-ui/themes"
import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Chunk, type DateTime, Effect, Option, Schema } from "effect"
import { Component, Form, Lens, SubmittableForm, Subscribable } from "effect-fc"
import * as Domain from "@/domain"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
import { TodoFormSchema } from "./TodoFormSchema"
import { TodosState } from "./TodosState"
const makeTodo = makeUuid4.pipe(
Effect.map(id => Domain.Todo.Todo.make({
id,
content: "",
completedAt: Option.none(),
})),
Effect.provide(GetRandomValues.CryptoRandom),
)
export class NewTodoView extends Component.make("NewTodoView")(function*() {
const state = yield* TodosState
const [
form,
contentField,
completedAtField,
] = yield* Component.useOnMount(() => Effect.gen(function*() {
const form = yield* SubmittableForm.service({
schema: TodoFormSchema,
initialEncodedValue: yield* Schema.encode(TodoFormSchema)(yield* makeTodo),
f: ([todo, form]) => Lens.update(state.lens, Chunk.prepend(todo)).pipe(
Effect.andThen(makeTodo),
Effect.andThen(Schema.encode(TodoFormSchema)),
Effect.andThen(v => Lens.set(form.encodedValue, v)),
),
})
return [
form,
Form.focusObjectOn(form, "content"),
Form.focusObjectOn(form, "completedAt"),
] as const
}))
const [canCommit] = yield* Subscribable.useAll([form.canCommit])
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
const TextFieldFormInput = yield* TextFieldFormInputView.use
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
return (
<Flex direction="row" align="center" gap="2">
<Box flexGrow="1">
<Flex direction="column" align="stretch" gap="2">
<TextFieldFormInput
form={contentField}
debounce="250 millis"
/>
<Flex direction="row" justify="center" align="center" gap="2">
<TextFieldOptionalFormInput
form={completedAtField}
type="datetime-local"
defaultValue=""
/>
<Button disabled={!canCommit} onClick={() => void runPromise(form.submit)}>
Add
</Button>
</Flex>
</Flex>
</Box>
</Flex>
)
}) {}
@@ -1,9 +0,0 @@
import { Schema } from "effect"
import * as Domain from "@/domain"
import { DateTimeUtcFromZonedInput } from "@/lib/schema"
export const TodoFormSchema = Schema.compose(Schema.Struct({
...Domain.Todo.Todo.fields,
completedAt: Schema.OptionFromSelf(DateTimeUtcFromZonedInput),
}), Domain.Todo.Todo)
+138
View File
@@ -0,0 +1,138 @@
import * as Domain from "@/domain"
import { TextFieldFormInputView } from "@/lib/form/TextFieldFormInputView"
import { TextFieldOptionalFormInputView } from "@/lib/form/TextFieldOptionalFormInputView"
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"
const TodoFormSchema = Schema.compose(Schema.Struct({
...Domain.Todo.Todo.fields,
completedAt: Schema.OptionFromSelf(DateTimeUtcFromZonedInput),
}), Domain.Todo.Todo)
const makeTodo = makeUuid4.pipe(
Effect.map(id => Domain.Todo.Todo.make({
id,
content: "",
completedAt: Option.none(),
})),
Effect.provide(GetRandomValues.CryptoRandom),
)
export type TodoProps = (
| { readonly _tag: "new" }
| { readonly _tag: "edit", readonly id: string }
)
export class TodoView extends Component.make("TodoView")(function*(props: TodoProps) {
const state = yield* TodosState
const [
indexRef,
form,
contentField,
completedAtField,
] = yield* Component.useOnChange(() => Effect.gen(function*() {
const indexRef = Match.value(props).pipe(
Match.tag("new", () => Subscribable.make({ get: Effect.succeed(-1), changes: Stream.make(-1) })),
Match.tag("edit", ({ id }) => state.getIndexSubscribable(id)),
Match.exhaustive,
)
const form = yield* Form.service({
schema: TodoFormSchema,
initialEncodedValue: yield* Schema.encode(TodoFormSchema)(
yield* Match.value(props).pipe(
Match.tag("new", () => makeTodo),
Match.tag("edit", ({ id }) => state.getElementRef(id)),
Match.exhaustive,
)
),
f: ([todo, form]) => Match.value(props).pipe(
Match.tag("new", () => Ref.update(state.ref, Chunk.prepend(todo)).pipe(
Effect.andThen(makeTodo),
Effect.andThen(Schema.encode(TodoFormSchema)),
Effect.andThen(v => Lens.set(form.encodedValue, v)),
)),
Match.tag("edit", ({ id }) => Ref.set(state.getElementRef(id), todo)),
Match.exhaustive,
),
autosubmit: props._tag === "edit",
})
return [
indexRef,
form,
Form.focusObjectField(form, "content"),
Form.focusObjectField(form, "completedAt"),
] as const
}), [props._tag, props._tag === "edit" ? props.id : undefined])
const [index, size, canSubmit] = yield* Subscribable.useSubscribables([
indexRef,
state.sizeSubscribable,
form.canSubmit,
])
const runSync = yield* Component.useRunSync()
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
const TextFieldFormInput = yield* TextFieldFormInputView.use
const TextFieldOptionalFormInput = yield* TextFieldOptionalFormInputView.use
return (
<Flex direction="row" align="center" gap="2">
<Box flexGrow="1">
<Flex direction="column" align="stretch" gap="2">
<TextFieldFormInput
form={contentField}
debounce="250 millis"
/>
<Flex direction="row" justify="center" align="center" gap="2">
<TextFieldOptionalFormInput
form={completedAtField}
type="datetime-local"
defaultValue=""
/>
{props._tag === "new" &&
<Button disabled={!canSubmit} onClick={() => void runPromise(form.submit)}>
Add
</Button>
}
</Flex>
</Flex>
</Box>
{props._tag === "edit" &&
<Flex direction="column" justify="center" align="center" gap="1">
<IconButton
disabled={index <= 0}
onClick={() => runSync(state.moveLeft(props.id))}
>
<FaArrowUp />
</IconButton>
<IconButton
disabled={index >= size - 1}
onClick={() => runSync(state.moveRight(props.id))}
>
<FaArrowDown />
</IconButton>
<IconButton onClick={() => runSync(state.remove(props.id))}>
<FaDeleteLeft />
</IconButton>
</Flex>
}
</Flex>
)
}) {}
+18 -20
View File
@@ -1,7 +1,7 @@
import { KeyValueStore } from "@effect/platform" import { KeyValueStore } from "@effect/platform"
import { BrowserKeyValueStore } from "@effect/platform-browser" import { BrowserKeyValueStore } from "@effect/platform-browser"
import { Chunk, Console, Effect, Option, Schema, Stream, SubscriptionRef } from "effect" import { Chunk, Console, Effect, Option, Schema, Stream, SubscriptionRef } from "effect"
import { Lens, Subscribable } from "effect-fc" import { Subscribable, SubscriptionSubRef } from "effect-fc"
import { Todo } from "@/domain" import { Todo } from "@/domain"
@@ -30,29 +30,27 @@ export class TodosState extends Effect.Service<TodosState>()("TodosState", {
: kv.remove(key) : kv.remove(key)
) )
const lens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(yield* readFromLocalStorage)) const ref = yield* SubscriptionRef.make(yield* readFromLocalStorage)
yield* Effect.forkScoped(lens.changes.pipe( yield* Effect.forkScoped(ref.changes.pipe(
Stream.debounce("500 millis"), Stream.debounce("500 millis"),
Stream.runForEach(saveToLocalStorage), Stream.runForEach(saveToLocalStorage),
)) ))
yield* Effect.addFinalizer(() => Lens.get(lens).pipe( yield* Effect.addFinalizer(() => ref.pipe(
Effect.andThen(saveToLocalStorage), Effect.andThen(saveToLocalStorage),
Effect.ignore, Effect.ignore,
)) ))
const sizeSubscribable = Subscribable.map(lens, Chunk.size) const sizeSubscribable = Subscribable.make({
get: Effect.andThen(ref, Chunk.size),
get changes() { return Stream.map(ref.changes, Chunk.size) },
})
const getElementRef = (id: string) => SubscriptionSubRef.makeFromChunkFindFirst(ref, v => v.id === id)
const getIndexSubscribable = (id: string) => Subscribable.make({
get: Effect.flatMap(ref, Chunk.findFirstIndex(v => v.id === id)),
get changes() { return Stream.flatMap(ref.changes, Chunk.findFirstIndex(v => v.id === id)) },
})
const getElementLens = (id: string) => Lens.mapEffect( const moveLeft = (id: string) => SubscriptionRef.updateEffect(ref, todos => Effect.Do.pipe(
lens,
Chunk.findFirst(v => v.id === id),
(a, b) => Effect.flatMap(
Chunk.findFirstIndex(a, v => v.id === id),
i => Chunk.replaceOption(a, i, b),
)
)
const getIndexSubscribable = (id: string) => Subscribable.mapEffect(lens, Chunk.findFirstIndex(v => v.id === id))
const moveLeft = (id: string) => Lens.updateEffect(lens, todos => Effect.Do.pipe(
Effect.bind("index", () => Chunk.findFirstIndex(todos, v => v.id === id)), Effect.bind("index", () => Chunk.findFirstIndex(todos, v => v.id === id)),
Effect.bind("todo", ({ index }) => Chunk.get(todos, index)), Effect.bind("todo", ({ index }) => Chunk.get(todos, index)),
Effect.bind("previous", ({ index }) => Chunk.get(todos, index - 1)), Effect.bind("previous", ({ index }) => Chunk.get(todos, index - 1)),
@@ -64,7 +62,7 @@ export class TodosState extends Effect.Service<TodosState>()("TodosState", {
: todos : todos
), ),
)) ))
const moveRight = (id: string) => Lens.updateEffect(lens, todos => Effect.Do.pipe( const moveRight = (id: string) => SubscriptionRef.updateEffect(ref, todos => Effect.Do.pipe(
Effect.bind("index", () => Chunk.findFirstIndex(todos, v => v.id === id)), Effect.bind("index", () => Chunk.findFirstIndex(todos, v => v.id === id)),
Effect.bind("todo", ({ index }) => Chunk.get(todos, index)), Effect.bind("todo", ({ index }) => Chunk.get(todos, index)),
Effect.bind("next", ({ index }) => Chunk.get(todos, index + 1)), Effect.bind("next", ({ index }) => Chunk.get(todos, index + 1)),
@@ -76,15 +74,15 @@ export class TodosState extends Effect.Service<TodosState>()("TodosState", {
: todos : todos
), ),
)) ))
const remove = (id: string) => Lens.updateEffect(lens, todos => Effect.andThen( const remove = (id: string) => SubscriptionRef.updateEffect(ref, todos => Effect.andThen(
Chunk.findFirstIndex(todos, v => v.id === id), Chunk.findFirstIndex(todos, v => v.id === id),
index => Chunk.remove(todos, index), index => Chunk.remove(todos, index),
)) ))
return { return {
lens, ref,
sizeSubscribable, sizeSubscribable,
getElementLens, getElementRef,
getIndexSubscribable, getIndexSubscribable,
moveLeft, moveLeft,
moveRight, moveRight,
+5 -7
View File
@@ -1,32 +1,30 @@
import { Container, Flex, Heading } from "@radix-ui/themes" import { Container, Flex, Heading } from "@radix-ui/themes"
import { Chunk, Console, Effect } from "effect" import { Chunk, Console, Effect } from "effect"
import { Component, Subscribable } from "effect-fc" import { Component, Subscribable } from "effect-fc"
import { EditTodoView } from "./EditTodoView"
import { NewTodoView } from "./NewTodoView"
import { TodosState } from "./TodosState" import { TodosState } from "./TodosState"
import { TodoView } from "./TodoView"
export class TodosView extends Component.make("TodosView")(function*() { export class TodosView extends Component.make("TodosView")(function*() {
const state = yield* TodosState const state = yield* TodosState
const [todos] = yield* Subscribable.useAll([state.lens]) const [todos] = yield* Subscribable.useSubscribables([state.ref])
yield* Component.useOnMount(() => Effect.andThen( yield* Component.useOnMount(() => Effect.andThen(
Console.log("Todos mounted"), Console.log("Todos mounted"),
Effect.addFinalizer(() => Console.log("Todos unmounted")), Effect.addFinalizer(() => Console.log("Todos unmounted")),
)) ))
const NewTodo = yield* NewTodoView.use const Todo = yield* TodoView.use
const EditTodo = yield* EditTodoView.use
return ( return (
<Container> <Container>
<Heading align="center">Todos</Heading> <Heading align="center">Todos</Heading>
<Flex direction="column" align="stretch" gap="2" mt="2"> <Flex direction="column" align="stretch" gap="2" mt="2">
<NewTodo /> <Todo _tag="new" />
{Chunk.map(todos, todo => {Chunk.map(todos, todo =>
<EditTodo key={todo.id} id={todo.id} /> <Todo key={todo.id} _tag="edit" id={todo.id} />
)} )}
</Flex> </Flex>
</Container> </Container>